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#[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 #[inline]
27 pub fn new<T: PyTypeInfo>(py: Python<'_>) -> Bound<'_, PyType> {
28 T::type_object(py)
29 }
30
31 #[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#[doc(alias = "PyType")]
57pub trait PyTypeMethods<'py>: crate::sealed::Sealed {
58 fn as_type_ptr(&self) -> *mut ffi::PyTypeObject;
60
61 fn name(&self) -> PyResult<Bound<'py, PyString>>;
63
64 fn qualname(&self) -> PyResult<Bound<'py, PyString>>;
67
68 fn module(&self) -> PyResult<Bound<'py, PyString>>;
70
71 fn fully_qualified_name(&self) -> PyResult<Bound<'py, PyString>>;
73
74 fn is_subclass(&self, other: &Bound<'_, PyAny>) -> PyResult<bool>;
78
79 fn is_subclass_of<T>(&self) -> PyResult<bool>
84 where
85 T: PyTypeInfo;
86
87 fn mro(&self) -> Bound<'py, PyTuple>;
91
92 fn bases(&self) -> Bound<'py, PyTuple>;
96}
97
98impl<'py> PyTypeMethods<'py> for Bound<'py, PyType> {
99 #[inline]
101 fn as_type_ptr(&self) -> *mut ffi::PyTypeObject {
102 self.as_ptr() as *mut ffi::PyTypeObject
103 }
104
105 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 .cast_into_unchecked()
117 };
118
119 Ok(name)
120 }
121
122 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 .cast_into_unchecked()
136 };
137
138 Ok(name)
139 }
140
141 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 name.cast_into().map_err(Into::into)
154 }
155
156 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 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 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}