1 | use crate::err::{self, PyResult}; |
2 | use crate::instance::Borrowed; |
3 | #[cfg (not(Py_3_13))] |
4 | use crate::pybacked::PyBackedStr; |
5 | use crate::types::any::PyAnyMethods; |
6 | use crate::types::PyTuple; |
7 | use crate::{ffi, Bound, PyAny, PyTypeInfo, Python}; |
8 | |
9 | use super::PyString; |
10 | |
11 | /// Represents a reference to a Python `type` object. |
12 | /// |
13 | /// Values of this type are accessed via PyO3's smart pointers, e.g. as |
14 | /// [`Py<PyType>`][crate::Py] or [`Bound<'py, PyType>`][Bound]. |
15 | /// |
16 | /// For APIs available on `type` objects, see the [`PyTypeMethods`] trait which is implemented for |
17 | /// [`Bound<'py, PyType>`][Bound]. |
18 | #[repr (transparent)] |
19 | pub struct PyType(PyAny); |
20 | |
21 | pyobject_native_type_core!(PyType, pyobject_native_static_type_object!(ffi::PyType_Type), #checkfunction=ffi::PyType_Check); |
22 | |
23 | impl PyType { |
24 | /// Creates a new type object. |
25 | #[inline ] |
26 | pub fn new<T: PyTypeInfo>(py: Python<'_>) -> Bound<'_, PyType> { |
27 | T::type_object(py) |
28 | } |
29 | |
30 | /// Deprecated name for [`PyType::new`]. |
31 | #[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 | /// Converts the given FFI pointer into `Bound<PyType>`, to use in safe code. |
38 | /// |
39 | /// The function creates a new reference from the given pointer, and returns |
40 | /// it as a `Bound<PyType>`. |
41 | /// |
42 | /// # Safety |
43 | /// - The pointer must be a valid non-null reference to a `PyTypeObject` |
44 | #[inline ] |
45 | pub unsafe fn from_borrowed_type_ptr( |
46 | py: Python<'_>, |
47 | p: *mut ffi::PyTypeObject, |
48 | ) -> Bound<'_, PyType> { |
49 | unsafe { |
50 | Borrowed::from_ptr_unchecked(py, p.cast()) |
51 | .downcast_unchecked() |
52 | .to_owned() |
53 | } |
54 | } |
55 | } |
56 | |
57 | /// Implementation of functionality for [`PyType`]. |
58 | /// |
59 | /// These methods are defined for the `Bound<'py, PyType>` smart pointer, so to use method call |
60 | /// syntax these methods are separated into a trait, because stable Rust does not yet support |
61 | /// `arbitrary_self_types`. |
62 | #[doc (alias = "PyType" )] |
63 | pub trait PyTypeMethods<'py>: crate::sealed::Sealed { |
64 | /// Retrieves the underlying FFI pointer associated with this Python object. |
65 | fn as_type_ptr(&self) -> *mut ffi::PyTypeObject; |
66 | |
67 | /// Gets the name of the `PyType`. Equivalent to `self.__name__` in Python. |
68 | fn name(&self) -> PyResult<Bound<'py, PyString>>; |
69 | |
70 | /// Gets the [qualified name](https://docs.python.org/3/glossary.html#term-qualified-name) of the `PyType`. |
71 | /// Equivalent to `self.__qualname__` in Python. |
72 | fn qualname(&self) -> PyResult<Bound<'py, PyString>>; |
73 | |
74 | /// Gets the name of the module defining the `PyType`. |
75 | fn module(&self) -> PyResult<Bound<'py, PyString>>; |
76 | |
77 | /// Gets the [fully qualified name](https://peps.python.org/pep-0737/#add-pytype-getfullyqualifiedname-function) of the `PyType`. |
78 | fn fully_qualified_name(&self) -> PyResult<Bound<'py, PyString>>; |
79 | |
80 | /// Checks whether `self` is a subclass of `other`. |
81 | /// |
82 | /// Equivalent to the Python expression `issubclass(self, other)`. |
83 | fn is_subclass(&self, other: &Bound<'_, PyAny>) -> PyResult<bool>; |
84 | |
85 | /// Checks whether `self` is a subclass of type `T`. |
86 | /// |
87 | /// Equivalent to the Python expression `issubclass(self, T)`, if the type |
88 | /// `T` is known at compile time. |
89 | fn is_subclass_of<T>(&self) -> PyResult<bool> |
90 | where |
91 | T: PyTypeInfo; |
92 | |
93 | /// Return the method resolution order for this type. |
94 | /// |
95 | /// Equivalent to the Python expression `self.__mro__`. |
96 | fn mro(&self) -> Bound<'py, PyTuple>; |
97 | |
98 | /// Return Python bases |
99 | /// |
100 | /// Equivalent to the Python expression `self.__bases__`. |
101 | fn bases(&self) -> Bound<'py, PyTuple>; |
102 | } |
103 | |
104 | impl<'py> PyTypeMethods<'py> for Bound<'py, PyType> { |
105 | /// Retrieves the underlying FFI pointer associated with this Python object. |
106 | #[inline ] |
107 | fn as_type_ptr(&self) -> *mut ffi::PyTypeObject { |
108 | self.as_ptr() as *mut ffi::PyTypeObject |
109 | } |
110 | |
111 | /// Gets the name of the `PyType`. |
112 | fn name(&self) -> PyResult<Bound<'py, PyString>> { |
113 | #[cfg (not(Py_3_11))] |
114 | let name = self |
115 | .getattr(intern!(self.py(), "__name__" ))? |
116 | .downcast_into()?; |
117 | |
118 | #[cfg (Py_3_11)] |
119 | let name = unsafe { |
120 | use crate::ffi_ptr_ext::FfiPtrExt; |
121 | ffi::PyType_GetName(self.as_type_ptr()) |
122 | .assume_owned_or_err(self.py())? |
123 | // SAFETY: setting `__name__` from Python is required to be a `str` |
124 | .downcast_into_unchecked() |
125 | }; |
126 | |
127 | Ok(name) |
128 | } |
129 | |
130 | /// Gets the [qualified name](https://docs.python.org/3/glossary.html#term-qualified-name) of the `PyType`. |
131 | fn qualname(&self) -> PyResult<Bound<'py, PyString>> { |
132 | #[cfg (not(Py_3_11))] |
133 | let name = self |
134 | .getattr(intern!(self.py(), "__qualname__" ))? |
135 | .downcast_into()?; |
136 | |
137 | #[cfg (Py_3_11)] |
138 | let name = unsafe { |
139 | use crate::ffi_ptr_ext::FfiPtrExt; |
140 | ffi::PyType_GetQualName(self.as_type_ptr()) |
141 | .assume_owned_or_err(self.py())? |
142 | // SAFETY: setting `__qualname__` from Python is required to be a `str` |
143 | .downcast_into_unchecked() |
144 | }; |
145 | |
146 | Ok(name) |
147 | } |
148 | |
149 | /// Gets the name of the module defining the `PyType`. |
150 | fn module(&self) -> PyResult<Bound<'py, PyString>> { |
151 | #[cfg (not(Py_3_13))] |
152 | let name = self.getattr(intern!(self.py(), "__module__" ))?; |
153 | |
154 | #[cfg (Py_3_13)] |
155 | let name = unsafe { |
156 | use crate::ffi_ptr_ext::FfiPtrExt; |
157 | ffi::PyType_GetModuleName(self.as_type_ptr()).assume_owned_or_err(self.py())? |
158 | }; |
159 | |
160 | // `__module__` is never guaranteed to be a `str` |
161 | name.downcast_into().map_err(Into::into) |
162 | } |
163 | |
164 | /// Gets the [fully qualified name](https://docs.python.org/3/glossary.html#term-qualified-name) of the `PyType`. |
165 | fn fully_qualified_name(&self) -> PyResult<Bound<'py, PyString>> { |
166 | #[cfg (not(Py_3_13))] |
167 | let name = { |
168 | let module = self.getattr(intern!(self.py(), "__module__" ))?; |
169 | let qualname = self.getattr(intern!(self.py(), "__qualname__" ))?; |
170 | |
171 | let module_str = module.extract::<PyBackedStr>()?; |
172 | if module_str == "builtins" || module_str == "__main__" { |
173 | qualname.downcast_into()? |
174 | } else { |
175 | PyString::new(self.py(), &format!(" {}. {}" , module, qualname)) |
176 | } |
177 | }; |
178 | |
179 | #[cfg (Py_3_13)] |
180 | let name = unsafe { |
181 | use crate::ffi_ptr_ext::FfiPtrExt; |
182 | ffi::PyType_GetFullyQualifiedName(self.as_type_ptr()) |
183 | .assume_owned_or_err(self.py())? |
184 | .downcast_into_unchecked() |
185 | }; |
186 | |
187 | Ok(name) |
188 | } |
189 | |
190 | /// Checks whether `self` is a subclass of `other`. |
191 | /// |
192 | /// Equivalent to the Python expression `issubclass(self, other)`. |
193 | fn is_subclass(&self, other: &Bound<'_, PyAny>) -> PyResult<bool> { |
194 | let result = unsafe { ffi::PyObject_IsSubclass(self.as_ptr(), other.as_ptr()) }; |
195 | err::error_on_minusone(self.py(), result)?; |
196 | Ok(result == 1) |
197 | } |
198 | |
199 | /// Checks whether `self` is a subclass of type `T`. |
200 | /// |
201 | /// Equivalent to the Python expression `issubclass(self, T)`, if the type |
202 | /// `T` is known at compile time. |
203 | fn is_subclass_of<T>(&self) -> PyResult<bool> |
204 | where |
205 | T: PyTypeInfo, |
206 | { |
207 | self.is_subclass(&T::type_object(self.py())) |
208 | } |
209 | |
210 | fn mro(&self) -> Bound<'py, PyTuple> { |
211 | #[cfg (any(Py_LIMITED_API, PyPy))] |
212 | let mro = self |
213 | .getattr(intern!(self.py(), "__mro__" )) |
214 | .expect("Cannot get `__mro__` from object." ) |
215 | .extract() |
216 | .expect("Unexpected type in `__mro__` attribute." ); |
217 | |
218 | #[cfg (not(any(Py_LIMITED_API, PyPy)))] |
219 | let mro = unsafe { |
220 | use crate::ffi_ptr_ext::FfiPtrExt; |
221 | (*self.as_type_ptr()) |
222 | .tp_mro |
223 | .assume_borrowed(self.py()) |
224 | .to_owned() |
225 | .downcast_into_unchecked() |
226 | }; |
227 | |
228 | mro |
229 | } |
230 | |
231 | fn bases(&self) -> Bound<'py, PyTuple> { |
232 | #[cfg (any(Py_LIMITED_API, PyPy))] |
233 | let bases = self |
234 | .getattr(intern!(self.py(), "__bases__" )) |
235 | .expect("Cannot get `__bases__` from object." ) |
236 | .extract() |
237 | .expect("Unexpected type in `__bases__` attribute." ); |
238 | |
239 | #[cfg (not(any(Py_LIMITED_API, PyPy)))] |
240 | let bases = unsafe { |
241 | use crate::ffi_ptr_ext::FfiPtrExt; |
242 | (*self.as_type_ptr()) |
243 | .tp_bases |
244 | .assume_borrowed(self.py()) |
245 | .to_owned() |
246 | .downcast_into_unchecked() |
247 | }; |
248 | |
249 | bases |
250 | } |
251 | } |
252 | |
253 | #[cfg (test)] |
254 | mod tests { |
255 | use crate::tests::common::generate_unique_module_name; |
256 | use crate::types::{PyAnyMethods, PyBool, PyInt, PyModule, PyTuple, PyType, PyTypeMethods}; |
257 | use crate::PyAny; |
258 | use crate::Python; |
259 | use pyo3_ffi::c_str; |
260 | |
261 | #[test ] |
262 | fn test_type_is_subclass() { |
263 | Python::with_gil(|py| { |
264 | let bool_type = py.get_type::<PyBool>(); |
265 | let long_type = py.get_type::<PyInt>(); |
266 | assert!(bool_type.is_subclass(&long_type).unwrap()); |
267 | }); |
268 | } |
269 | |
270 | #[test ] |
271 | fn test_type_is_subclass_of() { |
272 | Python::with_gil(|py| { |
273 | assert!(py.get_type::<PyBool>().is_subclass_of::<PyInt>().unwrap()); |
274 | }); |
275 | } |
276 | |
277 | #[test ] |
278 | fn test_mro() { |
279 | Python::with_gil(|py| { |
280 | assert!(py |
281 | .get_type::<PyBool>() |
282 | .mro() |
283 | .eq(PyTuple::new( |
284 | py, |
285 | [ |
286 | py.get_type::<PyBool>(), |
287 | py.get_type::<PyInt>(), |
288 | py.get_type::<PyAny>() |
289 | ] |
290 | ) |
291 | .unwrap()) |
292 | .unwrap()); |
293 | }); |
294 | } |
295 | |
296 | #[test ] |
297 | fn test_bases_bool() { |
298 | Python::with_gil(|py| { |
299 | assert!(py |
300 | .get_type::<PyBool>() |
301 | .bases() |
302 | .eq(PyTuple::new(py, [py.get_type::<PyInt>()]).unwrap()) |
303 | .unwrap()); |
304 | }); |
305 | } |
306 | |
307 | #[test ] |
308 | fn test_bases_object() { |
309 | Python::with_gil(|py| { |
310 | assert!(py |
311 | .get_type::<PyAny>() |
312 | .bases() |
313 | .eq(PyTuple::empty(py)) |
314 | .unwrap()); |
315 | }); |
316 | } |
317 | |
318 | #[test ] |
319 | fn test_type_names_standard() { |
320 | Python::with_gil(|py| { |
321 | let module_name = generate_unique_module_name("test_module" ); |
322 | let module = PyModule::from_code( |
323 | py, |
324 | c_str!( |
325 | r#" |
326 | class MyClass: |
327 | pass |
328 | "# |
329 | ), |
330 | c_str!(file!()), |
331 | &module_name, |
332 | ) |
333 | .expect("module create failed" ); |
334 | |
335 | let my_class = module.getattr("MyClass" ).unwrap(); |
336 | let my_class_type = my_class.downcast_into::<PyType>().unwrap(); |
337 | assert_eq!(my_class_type.name().unwrap(), "MyClass" ); |
338 | assert_eq!(my_class_type.qualname().unwrap(), "MyClass" ); |
339 | let module_name = module_name.to_str().unwrap(); |
340 | let qualname = format!("{module_name}.MyClass" ); |
341 | assert_eq!(my_class_type.module().unwrap(), module_name); |
342 | assert_eq!( |
343 | my_class_type.fully_qualified_name().unwrap(), |
344 | qualname.as_str() |
345 | ); |
346 | }); |
347 | } |
348 | |
349 | #[test ] |
350 | fn test_type_names_builtin() { |
351 | Python::with_gil(|py| { |
352 | let bool_type = py.get_type::<PyBool>(); |
353 | assert_eq!(bool_type.name().unwrap(), "bool" ); |
354 | assert_eq!(bool_type.qualname().unwrap(), "bool" ); |
355 | assert_eq!(bool_type.module().unwrap(), "builtins" ); |
356 | assert_eq!(bool_type.fully_qualified_name().unwrap(), "bool" ); |
357 | }); |
358 | } |
359 | |
360 | #[test ] |
361 | fn test_type_names_nested() { |
362 | Python::with_gil(|py| { |
363 | let module_name = generate_unique_module_name("test_module" ); |
364 | let module = PyModule::from_code( |
365 | py, |
366 | c_str!( |
367 | r#" |
368 | class OuterClass: |
369 | class InnerClass: |
370 | pass |
371 | "# |
372 | ), |
373 | c_str!(file!()), |
374 | &module_name, |
375 | ) |
376 | .expect("module create failed" ); |
377 | |
378 | let outer_class = module.getattr("OuterClass" ).unwrap(); |
379 | let inner_class = outer_class.getattr("InnerClass" ).unwrap(); |
380 | let inner_class_type = inner_class.downcast_into::<PyType>().unwrap(); |
381 | assert_eq!(inner_class_type.name().unwrap(), "InnerClass" ); |
382 | assert_eq!( |
383 | inner_class_type.qualname().unwrap(), |
384 | "OuterClass.InnerClass" |
385 | ); |
386 | let module_name = module_name.to_str().unwrap(); |
387 | let qualname = format!("{module_name}.OuterClass.InnerClass" ); |
388 | assert_eq!(inner_class_type.module().unwrap(), module_name); |
389 | assert_eq!( |
390 | inner_class_type.fully_qualified_name().unwrap(), |
391 | qualname.as_str() |
392 | ); |
393 | }); |
394 | } |
395 | } |
396 | |