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;
7#[cfg(feature = "gil-refs")]
8use crate::PyNativeType;
9use crate::{ffi, Bound, PyAny, PyTypeInfo, Python};
10
11use super::PyString;
12
13#[repr(transparent)]
21pub struct PyType(PyAny);
22
23pyobject_native_type_core!(PyType, pyobject_native_static_type_object!(ffi::PyType_Type), #checkfunction=ffi::PyType_Check);
24
25impl PyType {
26 #[inline]
28 pub fn new_bound<T: PyTypeInfo>(py: Python<'_>) -> Bound<'_, PyType> {
29 T::type_object_bound(py)
30 }
31
32 #[inline]
40 pub unsafe fn from_borrowed_type_ptr(
41 py: Python<'_>,
42 p: *mut ffi::PyTypeObject,
43 ) -> Bound<'_, PyType> {
44 Borrowed::from_ptr_unchecked(py, p.cast())
45 .downcast_unchecked()
46 .to_owned()
47 }
48}
49
50#[cfg(feature = "gil-refs")]
51impl PyType {
52 #[inline]
54 #[deprecated(
55 since = "0.21.0",
56 note = "`PyType::new` will be replaced by `PyType::new_bound` in a future PyO3 version"
57 )]
58 pub fn new<T: PyTypeInfo>(py: Python<'_>) -> &PyType {
59 T::type_object_bound(py).into_gil_ref()
60 }
61
62 #[inline]
64 pub fn as_type_ptr(&self) -> *mut ffi::PyTypeObject {
65 self.as_borrowed().as_type_ptr()
66 }
67
68 #[inline]
74 #[deprecated(
75 since = "0.21.0",
76 note = "Use `PyType::from_borrowed_type_ptr` instead"
77 )]
78 pub unsafe fn from_type_ptr(py: Python<'_>, p: *mut ffi::PyTypeObject) -> &PyType {
79 Self::from_borrowed_type_ptr(py, p).into_gil_ref()
80 }
81
82 pub fn name(&self) -> PyResult<&PyString> {
84 self.as_borrowed().name().map(Bound::into_gil_ref)
85 }
86
87 pub fn qualname(&self) -> PyResult<&PyString> {
90 self.as_borrowed().qualname().map(Bound::into_gil_ref)
91 }
92
93 pub fn is_subclass(&self, other: &PyAny) -> PyResult<bool> {
99 self.as_borrowed().is_subclass(&other.as_borrowed())
100 }
101
102 pub fn is_subclass_of<T>(&self) -> PyResult<bool>
107 where
108 T: PyTypeInfo,
109 {
110 self.as_borrowed().is_subclass_of::<T>()
111 }
112}
113
114#[doc(alias = "PyType")]
120pub trait PyTypeMethods<'py>: crate::sealed::Sealed {
121 fn as_type_ptr(&self) -> *mut ffi::PyTypeObject;
123
124 fn name(&self) -> PyResult<Bound<'py, PyString>>;
126
127 fn qualname(&self) -> PyResult<Bound<'py, PyString>>;
130
131 fn module(&self) -> PyResult<Bound<'py, PyString>>;
133
134 fn fully_qualified_name(&self) -> PyResult<Bound<'py, PyString>>;
136
137 fn is_subclass(&self, other: &Bound<'_, PyAny>) -> PyResult<bool>;
141
142 fn is_subclass_of<T>(&self) -> PyResult<bool>
147 where
148 T: PyTypeInfo;
149
150 fn mro(&self) -> Bound<'py, PyTuple>;
154
155 fn bases(&self) -> Bound<'py, PyTuple>;
159}
160
161impl<'py> PyTypeMethods<'py> for Bound<'py, PyType> {
162 #[inline]
164 fn as_type_ptr(&self) -> *mut ffi::PyTypeObject {
165 self.as_ptr() as *mut ffi::PyTypeObject
166 }
167
168 fn name(&self) -> PyResult<Bound<'py, PyString>> {
170 #[cfg(not(Py_3_11))]
171 let name = self
172 .getattr(intern!(self.py(), "__name__"))?
173 .downcast_into()?;
174
175 #[cfg(Py_3_11)]
176 let name = unsafe {
177 use crate::ffi_ptr_ext::FfiPtrExt;
178 ffi::PyType_GetName(self.as_type_ptr())
179 .assume_owned_or_err(self.py())?
180 .downcast_into_unchecked()
182 };
183
184 Ok(name)
185 }
186
187 fn qualname(&self) -> PyResult<Bound<'py, PyString>> {
189 #[cfg(not(Py_3_11))]
190 let name = self
191 .getattr(intern!(self.py(), "__qualname__"))?
192 .downcast_into()?;
193
194 #[cfg(Py_3_11)]
195 let name = unsafe {
196 use crate::ffi_ptr_ext::FfiPtrExt;
197 ffi::PyType_GetQualName(self.as_type_ptr())
198 .assume_owned_or_err(self.py())?
199 .downcast_into_unchecked()
201 };
202
203 Ok(name)
204 }
205
206 fn module(&self) -> PyResult<Bound<'py, PyString>> {
208 #[cfg(not(Py_3_13))]
209 let name = self.getattr(intern!(self.py(), "__module__"))?;
210
211 #[cfg(Py_3_13)]
212 let name = unsafe {
213 use crate::ffi_ptr_ext::FfiPtrExt;
214 ffi::PyType_GetModuleName(self.as_type_ptr()).assume_owned_or_err(self.py())?
215 };
216
217 name.downcast_into().map_err(Into::into)
219 }
220
221 fn fully_qualified_name(&self) -> PyResult<Bound<'py, PyString>> {
223 #[cfg(not(Py_3_13))]
224 let name = {
225 let module = self.getattr(intern!(self.py(), "__module__"))?;
226 let qualname = self.getattr(intern!(self.py(), "__qualname__"))?;
227
228 let module_str = module.extract::<PyBackedStr>()?;
229 if module_str == "builtins" || module_str == "__main__" {
230 qualname.downcast_into()?
231 } else {
232 PyString::new_bound(self.py(), &format!("{}.{}", module, qualname))
233 }
234 };
235
236 #[cfg(Py_3_13)]
237 let name = unsafe {
238 use crate::ffi_ptr_ext::FfiPtrExt;
239 ffi::PyType_GetFullyQualifiedName(self.as_type_ptr())
240 .assume_owned_or_err(self.py())?
241 .downcast_into_unchecked()
242 };
243
244 Ok(name)
245 }
246
247 fn is_subclass(&self, other: &Bound<'_, PyAny>) -> PyResult<bool> {
251 let result = unsafe { ffi::PyObject_IsSubclass(self.as_ptr(), other.as_ptr()) };
252 err::error_on_minusone(self.py(), result)?;
253 Ok(result == 1)
254 }
255
256 fn is_subclass_of<T>(&self) -> PyResult<bool>
261 where
262 T: PyTypeInfo,
263 {
264 self.is_subclass(&T::type_object_bound(self.py()))
265 }
266
267 fn mro(&self) -> Bound<'py, PyTuple> {
268 #[cfg(any(Py_LIMITED_API, PyPy))]
269 let mro = self
270 .getattr(intern!(self.py(), "__mro__"))
271 .expect("Cannot get `__mro__` from object.")
272 .extract()
273 .expect("Unexpected type in `__mro__` attribute.");
274
275 #[cfg(not(any(Py_LIMITED_API, PyPy)))]
276 let mro = unsafe {
277 use crate::ffi_ptr_ext::FfiPtrExt;
278 (*self.as_type_ptr())
279 .tp_mro
280 .assume_borrowed(self.py())
281 .to_owned()
282 .downcast_into_unchecked()
283 };
284
285 mro
286 }
287
288 fn bases(&self) -> Bound<'py, PyTuple> {
289 #[cfg(any(Py_LIMITED_API, PyPy))]
290 let bases = self
291 .getattr(intern!(self.py(), "__bases__"))
292 .expect("Cannot get `__bases__` from object.")
293 .extract()
294 .expect("Unexpected type in `__bases__` attribute.");
295
296 #[cfg(not(any(Py_LIMITED_API, PyPy)))]
297 let bases = unsafe {
298 use crate::ffi_ptr_ext::FfiPtrExt;
299 (*self.as_type_ptr())
300 .tp_bases
301 .assume_borrowed(self.py())
302 .to_owned()
303 .downcast_into_unchecked()
304 };
305
306 bases
307 }
308}
309
310#[cfg(test)]
311mod tests {
312 use crate::types::{
313 PyAnyMethods, PyBool, PyInt, PyLong, PyModule, PyTuple, PyType, PyTypeMethods,
314 };
315 use crate::PyAny;
316 use crate::Python;
317
318 #[test]
319 fn test_type_is_subclass() {
320 Python::with_gil(|py| {
321 let bool_type = py.get_type_bound::<PyBool>();
322 let long_type = py.get_type_bound::<PyLong>();
323 assert!(bool_type.is_subclass(&long_type).unwrap());
324 });
325 }
326
327 #[test]
328 fn test_type_is_subclass_of() {
329 Python::with_gil(|py| {
330 assert!(py
331 .get_type_bound::<PyBool>()
332 .is_subclass_of::<PyLong>()
333 .unwrap());
334 });
335 }
336
337 #[test]
338 fn test_mro() {
339 Python::with_gil(|py| {
340 assert!(py
341 .get_type_bound::<PyBool>()
342 .mro()
343 .eq(PyTuple::new_bound(
344 py,
345 [
346 py.get_type_bound::<PyBool>(),
347 py.get_type_bound::<PyInt>(),
348 py.get_type_bound::<PyAny>()
349 ]
350 ))
351 .unwrap());
352 });
353 }
354
355 #[test]
356 fn test_bases_bool() {
357 Python::with_gil(|py| {
358 assert!(py
359 .get_type_bound::<PyBool>()
360 .bases()
361 .eq(PyTuple::new_bound(py, [py.get_type_bound::<PyInt>()]))
362 .unwrap());
363 });
364 }
365
366 #[test]
367 fn test_bases_object() {
368 Python::with_gil(|py| {
369 assert!(py
370 .get_type_bound::<PyAny>()
371 .bases()
372 .eq(PyTuple::empty_bound(py))
373 .unwrap());
374 });
375 }
376
377 #[test]
378 fn test_type_names_standard() {
379 Python::with_gil(|py| {
380 let module = PyModule::from_code_bound(
381 py,
382 r#"
383class MyClass:
384 pass
385"#,
386 file!(),
387 "test_module",
388 )
389 .expect("module create failed");
390
391 let my_class = module.getattr("MyClass").unwrap();
392 let my_class_type = my_class.downcast_into::<PyType>().unwrap();
393 assert_eq!(my_class_type.name().unwrap(), "MyClass");
394 assert_eq!(my_class_type.qualname().unwrap(), "MyClass");
395 assert_eq!(my_class_type.module().unwrap(), "test_module");
396 assert_eq!(
397 my_class_type.fully_qualified_name().unwrap(),
398 "test_module.MyClass"
399 );
400 });
401 }
402
403 #[test]
404 fn test_type_names_builtin() {
405 Python::with_gil(|py| {
406 let bool_type = py.get_type_bound::<PyBool>();
407 assert_eq!(bool_type.name().unwrap(), "bool");
408 assert_eq!(bool_type.qualname().unwrap(), "bool");
409 assert_eq!(bool_type.module().unwrap(), "builtins");
410 assert_eq!(bool_type.fully_qualified_name().unwrap(), "bool");
411 });
412 }
413
414 #[test]
415 fn test_type_names_nested() {
416 Python::with_gil(|py| {
417 let module = PyModule::from_code_bound(
418 py,
419 r#"
420class OuterClass:
421 class InnerClass:
422 pass
423"#,
424 file!(),
425 "test_module",
426 )
427 .expect("module create failed");
428
429 let outer_class = module.getattr("OuterClass").unwrap();
430 let inner_class = outer_class.getattr("InnerClass").unwrap();
431 let inner_class_type = inner_class.downcast_into::<PyType>().unwrap();
432 assert_eq!(inner_class_type.name().unwrap(), "InnerClass");
433 assert_eq!(
434 inner_class_type.qualname().unwrap(),
435 "OuterClass.InnerClass"
436 );
437 assert_eq!(inner_class_type.module().unwrap(), "test_module");
438 assert_eq!(
439 inner_class_type.fully_qualified_name().unwrap(),
440 "test_module.OuterClass.InnerClass"
441 );
442 });
443 }
444}