use crate::err::{self, PyResult};
use crate::instance::Borrowed;
#[cfg(not(Py_3_13))]
use crate::pybacked::PyBackedStr;
use crate::types::any::PyAnyMethods;
use crate::types::PyTuple;
use crate::{ffi, Bound, PyAny, PyTypeInfo, Python};
use super::PyString;
#[repr(transparent)]
pub struct PyType(PyAny);
pyobject_native_type_core!(PyType, pyobject_native_static_type_object!(ffi::PyType_Type), #checkfunction=ffi::PyType_Check);
impl PyType {
#[inline]
pub fn new<T: PyTypeInfo>(py: Python<'_>) -> Bound<'_, PyType> {
T::type_object(py)
}
#[deprecated(since = "0.23.0", note = "renamed to `PyType::new`")]
#[inline]
pub fn new_bound<T: PyTypeInfo>(py: Python<'_>) -> Bound<'_, PyType> {
Self::new::<T>(py)
}
#[inline]
pub unsafe fn from_borrowed_type_ptr(
py: Python<'_>,
p: *mut ffi::PyTypeObject,
) -> Bound<'_, PyType> {
Borrowed::from_ptr_unchecked(py, p.cast())
.downcast_unchecked()
.to_owned()
}
}
#[doc(alias = "PyType")]
pub trait PyTypeMethods<'py>: crate::sealed::Sealed {
fn as_type_ptr(&self) -> *mut ffi::PyTypeObject;
fn name(&self) -> PyResult<Bound<'py, PyString>>;
fn qualname(&self) -> PyResult<Bound<'py, PyString>>;
fn module(&self) -> PyResult<Bound<'py, PyString>>;
fn fully_qualified_name(&self) -> PyResult<Bound<'py, PyString>>;
fn is_subclass(&self, other: &Bound<'_, PyAny>) -> PyResult<bool>;
fn is_subclass_of<T>(&self) -> PyResult<bool>
where
T: PyTypeInfo;
fn mro(&self) -> Bound<'py, PyTuple>;
fn bases(&self) -> Bound<'py, PyTuple>;
}
impl<'py> PyTypeMethods<'py> for Bound<'py, PyType> {
#[inline]
fn as_type_ptr(&self) -> *mut ffi::PyTypeObject {
self.as_ptr() as *mut ffi::PyTypeObject
}
fn name(&self) -> PyResult<Bound<'py, PyString>> {
#[cfg(not(Py_3_11))]
let name = self
.getattr(intern!(self.py(), "__name__"))?
.downcast_into()?;
#[cfg(Py_3_11)]
let name = unsafe {
use crate::ffi_ptr_ext::FfiPtrExt;
ffi::PyType_GetName(self.as_type_ptr())
.assume_owned_or_err(self.py())?
.downcast_into_unchecked()
};
Ok(name)
}
fn qualname(&self) -> PyResult<Bound<'py, PyString>> {
#[cfg(not(Py_3_11))]
let name = self
.getattr(intern!(self.py(), "__qualname__"))?
.downcast_into()?;
#[cfg(Py_3_11)]
let name = unsafe {
use crate::ffi_ptr_ext::FfiPtrExt;
ffi::PyType_GetQualName(self.as_type_ptr())
.assume_owned_or_err(self.py())?
.downcast_into_unchecked()
};
Ok(name)
}
fn module(&self) -> PyResult<Bound<'py, PyString>> {
#[cfg(not(Py_3_13))]
let name = self.getattr(intern!(self.py(), "__module__"))?;
#[cfg(Py_3_13)]
let name = unsafe {
use crate::ffi_ptr_ext::FfiPtrExt;
ffi::PyType_GetModuleName(self.as_type_ptr()).assume_owned_or_err(self.py())?
};
name.downcast_into().map_err(Into::into)
}
fn fully_qualified_name(&self) -> PyResult<Bound<'py, PyString>> {
#[cfg(not(Py_3_13))]
let name = {
let module = self.getattr(intern!(self.py(), "__module__"))?;
let qualname = self.getattr(intern!(self.py(), "__qualname__"))?;
let module_str = module.extract::<PyBackedStr>()?;
if module_str == "builtins" || module_str == "__main__" {
qualname.downcast_into()?
} else {
PyString::new(self.py(), &format!("{}.{}", module, qualname))
}
};
#[cfg(Py_3_13)]
let name = unsafe {
use crate::ffi_ptr_ext::FfiPtrExt;
ffi::PyType_GetFullyQualifiedName(self.as_type_ptr())
.assume_owned_or_err(self.py())?
.downcast_into_unchecked()
};
Ok(name)
}
fn is_subclass(&self, other: &Bound<'_, PyAny>) -> PyResult<bool> {
let result = unsafe { ffi::PyObject_IsSubclass(self.as_ptr(), other.as_ptr()) };
err::error_on_minusone(self.py(), result)?;
Ok(result == 1)
}
fn is_subclass_of<T>(&self) -> PyResult<bool>
where
T: PyTypeInfo,
{
self.is_subclass(&T::type_object(self.py()))
}
fn mro(&self) -> Bound<'py, PyTuple> {
#[cfg(any(Py_LIMITED_API, PyPy))]
let mro = self
.getattr(intern!(self.py(), "__mro__"))
.expect("Cannot get `__mro__` from object.")
.extract()
.expect("Unexpected type in `__mro__` attribute.");
#[cfg(not(any(Py_LIMITED_API, PyPy)))]
let mro = unsafe {
use crate::ffi_ptr_ext::FfiPtrExt;
(*self.as_type_ptr())
.tp_mro
.assume_borrowed(self.py())
.to_owned()
.downcast_into_unchecked()
};
mro
}
fn bases(&self) -> Bound<'py, PyTuple> {
#[cfg(any(Py_LIMITED_API, PyPy))]
let bases = self
.getattr(intern!(self.py(), "__bases__"))
.expect("Cannot get `__bases__` from object.")
.extract()
.expect("Unexpected type in `__bases__` attribute.");
#[cfg(not(any(Py_LIMITED_API, PyPy)))]
let bases = unsafe {
use crate::ffi_ptr_ext::FfiPtrExt;
(*self.as_type_ptr())
.tp_bases
.assume_borrowed(self.py())
.to_owned()
.downcast_into_unchecked()
};
bases
}
}
#[cfg(test)]
mod tests {
use crate::tests::common::generate_unique_module_name;
use crate::types::{PyAnyMethods, PyBool, PyInt, PyModule, PyTuple, PyType, PyTypeMethods};
use crate::PyAny;
use crate::Python;
use pyo3_ffi::c_str;
#[test]
fn test_type_is_subclass() {
Python::with_gil(|py| {
let bool_type = py.get_type::<PyBool>();
let long_type = py.get_type::<PyInt>();
assert!(bool_type.is_subclass(&long_type).unwrap());
});
}
#[test]
fn test_type_is_subclass_of() {
Python::with_gil(|py| {
assert!(py.get_type::<PyBool>().is_subclass_of::<PyInt>().unwrap());
});
}
#[test]
fn test_mro() {
Python::with_gil(|py| {
assert!(py
.get_type::<PyBool>()
.mro()
.eq(PyTuple::new(
py,
[
py.get_type::<PyBool>(),
py.get_type::<PyInt>(),
py.get_type::<PyAny>()
]
)
.unwrap())
.unwrap());
});
}
#[test]
fn test_bases_bool() {
Python::with_gil(|py| {
assert!(py
.get_type::<PyBool>()
.bases()
.eq(PyTuple::new(py, [py.get_type::<PyInt>()]).unwrap())
.unwrap());
});
}
#[test]
fn test_bases_object() {
Python::with_gil(|py| {
assert!(py
.get_type::<PyAny>()
.bases()
.eq(PyTuple::empty(py))
.unwrap());
});
}
#[test]
fn test_type_names_standard() {
Python::with_gil(|py| {
let module_name = generate_unique_module_name("test_module");
let module = PyModule::from_code(
py,
c_str!(
r#"
class MyClass:
pass
"#
),
c_str!(file!()),
&module_name,
)
.expect("module create failed");
let my_class = module.getattr("MyClass").unwrap();
let my_class_type = my_class.downcast_into::<PyType>().unwrap();
assert_eq!(my_class_type.name().unwrap(), "MyClass");
assert_eq!(my_class_type.qualname().unwrap(), "MyClass");
let module_name = module_name.to_str().unwrap();
let qualname = format!("{module_name}.MyClass");
assert_eq!(my_class_type.module().unwrap(), module_name);
assert_eq!(
my_class_type.fully_qualified_name().unwrap(),
qualname.as_str()
);
});
}
#[test]
fn test_type_names_builtin() {
Python::with_gil(|py| {
let bool_type = py.get_type::<PyBool>();
assert_eq!(bool_type.name().unwrap(), "bool");
assert_eq!(bool_type.qualname().unwrap(), "bool");
assert_eq!(bool_type.module().unwrap(), "builtins");
assert_eq!(bool_type.fully_qualified_name().unwrap(), "bool");
});
}
#[test]
fn test_type_names_nested() {
Python::with_gil(|py| {
let module_name = generate_unique_module_name("test_module");
let module = PyModule::from_code(
py,
c_str!(
r#"
class OuterClass:
class InnerClass:
pass
"#
),
c_str!(file!()),
&module_name,
)
.expect("module create failed");
let outer_class = module.getattr("OuterClass").unwrap();
let inner_class = outer_class.getattr("InnerClass").unwrap();
let inner_class_type = inner_class.downcast_into::<PyType>().unwrap();
assert_eq!(inner_class_type.name().unwrap(), "InnerClass");
assert_eq!(
inner_class_type.qualname().unwrap(),
"OuterClass.InnerClass"
);
let module_name = module_name.to_str().unwrap();
let qualname = format!("{module_name}.OuterClass.InnerClass");
assert_eq!(inner_class_type.module().unwrap(), module_name);
assert_eq!(
inner_class_type.fully_qualified_name().unwrap(),
qualname.as_str()
);
});
}
}