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#[cfg(RustPython)]
10use crate::{sync::PyOnceLock, Py};
11
12use super::PyString;
13
14#[repr(transparent)]
22pub struct PyType(PyAny);
23
24#[cfg(not(RustPython))]
25pyobject_native_type_core!(PyType, pyobject_native_static_type_object!(ffi::PyType_Type), "builtins", "type", #checkfunction=ffi::PyType_Check);
26
27#[cfg(RustPython)]
28pyobject_native_type_core!(
29 PyType,
30 |py| {
31 static TYPE: PyOnceLock<Py<PyType>> = PyOnceLock::new();
32 TYPE.import(py, "builtins", "type").unwrap().as_type_ptr()
33 },
34 "builtins",
35 "type",
36 #checkfunction=ffi::PyType_Check
37);
38
39impl PyType {
40 #[inline]
42 pub fn new<T: PyTypeInfo>(py: Python<'_>) -> Bound<'_, PyType> {
43 T::type_object(py)
44 }
45
46 #[inline]
54 pub unsafe fn from_borrowed_type_ptr(
55 py: Python<'_>,
56 p: *mut ffi::PyTypeObject,
57 ) -> Bound<'_, PyType> {
58 unsafe {
59 Borrowed::from_ptr_unchecked(py, p.cast())
60 .cast_unchecked()
61 .to_owned()
62 }
63 }
64}
65
66#[doc(alias = "PyType")]
72pub trait PyTypeMethods<'py>: crate::sealed::Sealed {
73 fn as_type_ptr(&self) -> *mut ffi::PyTypeObject;
75
76 fn name(&self) -> PyResult<Bound<'py, PyString>>;
78
79 fn qualname(&self) -> PyResult<Bound<'py, PyString>>;
82
83 fn module(&self) -> PyResult<Bound<'py, PyString>>;
85
86 fn fully_qualified_name(&self) -> PyResult<Bound<'py, PyString>>;
88
89 fn is_subclass(&self, other: &Bound<'_, PyAny>) -> PyResult<bool>;
93
94 fn is_subclass_of<T>(&self) -> PyResult<bool>
99 where
100 T: PyTypeInfo;
101
102 fn mro(&self) -> Bound<'py, PyTuple>;
106
107 fn bases(&self) -> Bound<'py, PyTuple>;
111}
112
113impl<'py> PyTypeMethods<'py> for Bound<'py, PyType> {
114 #[inline]
116 fn as_type_ptr(&self) -> *mut ffi::PyTypeObject {
117 self.as_ptr() as *mut ffi::PyTypeObject
118 }
119
120 fn name(&self) -> PyResult<Bound<'py, PyString>> {
122 #[cfg(not(Py_3_11))]
123 let name = self.getattr(intern!(self.py(), "__name__"))?.cast_into()?;
124
125 #[cfg(Py_3_11)]
126 let name = unsafe {
127 use crate::ffi_ptr_ext::FfiPtrExt;
128 ffi::PyType_GetName(self.as_type_ptr())
129 .assume_owned_or_err(self.py())?
130 .cast_into_unchecked()
132 };
133
134 Ok(name)
135 }
136
137 fn qualname(&self) -> PyResult<Bound<'py, PyString>> {
139 #[cfg(not(Py_3_11))]
140 let name = self
141 .getattr(intern!(self.py(), "__qualname__"))?
142 .cast_into()?;
143
144 #[cfg(Py_3_11)]
145 let name = unsafe {
146 use crate::ffi_ptr_ext::FfiPtrExt;
147 ffi::PyType_GetQualName(self.as_type_ptr())
148 .assume_owned_or_err(self.py())?
149 .cast_into_unchecked()
151 };
152
153 Ok(name)
154 }
155
156 fn module(&self) -> PyResult<Bound<'py, PyString>> {
158 #[cfg(not(Py_3_13))]
159 let name = self.getattr(intern!(self.py(), "__module__"))?;
160
161 #[cfg(Py_3_13)]
162 let name = unsafe {
163 use crate::ffi_ptr_ext::FfiPtrExt;
164 ffi::PyType_GetModuleName(self.as_type_ptr()).assume_owned_or_err(self.py())?
165 };
166
167 name.cast_into().map_err(Into::into)
169 }
170
171 fn fully_qualified_name(&self) -> PyResult<Bound<'py, PyString>> {
173 #[cfg(not(Py_3_13))]
174 let name = {
175 let module = self.getattr(intern!(self.py(), "__module__"))?;
176 let qualname = self.getattr(intern!(self.py(), "__qualname__"))?;
177
178 let module_str = module.extract::<PyBackedStr>()?;
179 if module_str == "builtins" || module_str == "__main__" {
180 qualname.cast_into()?
181 } else {
182 PyString::new(self.py(), &format!("{module}.{qualname}"))
183 }
184 };
185
186 #[cfg(Py_3_13)]
187 let name = unsafe {
188 use crate::ffi_ptr_ext::FfiPtrExt;
189 ffi::PyType_GetFullyQualifiedName(self.as_type_ptr())
190 .assume_owned_or_err(self.py())?
191 .cast_into_unchecked()
192 };
193
194 Ok(name)
195 }
196
197 fn is_subclass(&self, other: &Bound<'_, PyAny>) -> PyResult<bool> {
201 let result = unsafe { ffi::PyObject_IsSubclass(self.as_ptr(), other.as_ptr()) };
202 err::error_on_minusone(self.py(), result)?;
203 Ok(result == 1)
204 }
205
206 fn is_subclass_of<T>(&self) -> PyResult<bool>
211 where
212 T: PyTypeInfo,
213 {
214 self.is_subclass(&T::type_object(self.py()))
215 }
216
217 fn mro(&self) -> Bound<'py, PyTuple> {
218 #[cfg(any(Py_LIMITED_API, PyPy))]
219 let mro = self
220 .getattr(intern!(self.py(), "__mro__"))
221 .expect("Cannot get `__mro__` from object.")
222 .extract()
223 .expect("Unexpected type in `__mro__` attribute.");
224
225 #[cfg(not(any(Py_LIMITED_API, PyPy)))]
226 let mro = unsafe {
227 use crate::ffi_ptr_ext::FfiPtrExt;
228 (*self.as_type_ptr())
229 .tp_mro
230 .assume_borrowed(self.py())
231 .to_owned()
232 .cast_into_unchecked()
233 };
234
235 mro
236 }
237
238 fn bases(&self) -> Bound<'py, PyTuple> {
239 #[cfg(any(Py_LIMITED_API, PyPy))]
240 let bases = self
241 .getattr(intern!(self.py(), "__bases__"))
242 .expect("Cannot get `__bases__` from object.")
243 .extract()
244 .expect("Unexpected type in `__bases__` attribute.");
245
246 #[cfg(not(any(Py_LIMITED_API, PyPy)))]
247 let bases = unsafe {
248 use crate::ffi_ptr_ext::FfiPtrExt;
249 (*self.as_type_ptr())
250 .tp_bases
251 .assume_borrowed(self.py())
252 .to_owned()
253 .cast_into_unchecked()
254 };
255
256 bases
257 }
258}
259
260#[cfg(test)]
261mod tests {
262 use crate::test_utils::generate_unique_module_name;
263 use crate::types::{PyAnyMethods, PyBool, PyInt, PyModule, PyTuple, PyType, PyTypeMethods};
264 use crate::PyAny;
265 use crate::Python;
266 use pyo3_ffi::c_str;
267
268 #[test]
269 fn test_type_is_subclass() {
270 Python::attach(|py| {
271 let bool_type = py.get_type::<PyBool>();
272 let long_type = py.get_type::<PyInt>();
273 assert!(bool_type.is_subclass(&long_type).unwrap());
274 });
275 }
276
277 #[test]
278 fn test_type_is_subclass_of() {
279 Python::attach(|py| {
280 assert!(py.get_type::<PyBool>().is_subclass_of::<PyInt>().unwrap());
281 });
282 }
283
284 #[test]
285 fn test_mro() {
286 Python::attach(|py| {
287 assert!(py
288 .get_type::<PyBool>()
289 .mro()
290 .eq(PyTuple::new(
291 py,
292 [
293 py.get_type::<PyBool>(),
294 py.get_type::<PyInt>(),
295 py.get_type::<PyAny>()
296 ]
297 )
298 .unwrap())
299 .unwrap());
300 });
301 }
302
303 #[test]
304 fn test_bases_bool() {
305 Python::attach(|py| {
306 assert!(py
307 .get_type::<PyBool>()
308 .bases()
309 .eq(PyTuple::new(py, [py.get_type::<PyInt>()]).unwrap())
310 .unwrap());
311 });
312 }
313
314 #[test]
315 fn test_bases_object() {
316 Python::attach(|py| {
317 assert!(py
318 .get_type::<PyAny>()
319 .bases()
320 .eq(PyTuple::empty(py))
321 .unwrap());
322 });
323 }
324
325 #[test]
326 fn test_type_names_standard() {
327 Python::attach(|py| {
328 let module_name = generate_unique_module_name("test_module");
329 let module = PyModule::from_code(
330 py,
331 cr#"
332class MyClass:
333 pass
334"#,
335 c_str!(file!()),
336 &module_name,
337 )
338 .expect("module create failed");
339
340 let my_class = module.getattr("MyClass").unwrap();
341 let my_class_type = my_class.cast_into::<PyType>().unwrap();
342 assert_eq!(my_class_type.name().unwrap(), "MyClass");
343 assert_eq!(my_class_type.qualname().unwrap(), "MyClass");
344 let module_name = module_name.to_str().unwrap();
345 let qualname = format!("{module_name}.MyClass");
346 assert_eq!(my_class_type.module().unwrap(), module_name);
347 assert_eq!(
348 my_class_type.fully_qualified_name().unwrap(),
349 qualname.as_str()
350 );
351 });
352 }
353
354 #[test]
355 fn test_type_names_builtin() {
356 Python::attach(|py| {
357 let bool_type = py.get_type::<PyBool>();
358 assert_eq!(bool_type.name().unwrap(), "bool");
359 assert_eq!(bool_type.qualname().unwrap(), "bool");
360 assert_eq!(bool_type.module().unwrap(), "builtins");
361 assert_eq!(bool_type.fully_qualified_name().unwrap(), "bool");
362 });
363 }
364
365 #[test]
366 fn test_type_names_nested() {
367 Python::attach(|py| {
368 let module_name = generate_unique_module_name("test_module");
369 let module = PyModule::from_code(
370 py,
371 cr#"
372class OuterClass:
373 class InnerClass:
374 pass
375"#,
376 c_str!(file!()),
377 &module_name,
378 )
379 .expect("module create failed");
380
381 let outer_class = module.getattr("OuterClass").unwrap();
382 let inner_class = outer_class.getattr("InnerClass").unwrap();
383 let inner_class_type = inner_class.cast_into::<PyType>().unwrap();
384 assert_eq!(inner_class_type.name().unwrap(), "InnerClass");
385 assert_eq!(
386 inner_class_type.qualname().unwrap(),
387 "OuterClass.InnerClass"
388 );
389 let module_name = module_name.to_str().unwrap();
390 let qualname = format!("{module_name}.OuterClass.InnerClass");
391 assert_eq!(inner_class_type.module().unwrap(), module_name);
392 assert_eq!(
393 inner_class_type.fully_qualified_name().unwrap(),
394 qualname.as_str()
395 );
396 });
397 }
398}