@@ -8,6 +8,9 @@ use crate::{ffi, Bound, PyAny, PyTypeInfo, Python};
88
99use super :: PyString ;
1010
11+ #[ cfg( not( Py_LIMITED_API ) ) ]
12+ use super :: PyDict ;
13+
1114/// Represents a reference to a Python `type` object.
1215///
1316/// Values of this type are accessed via PyO3's smart pointers, e.g. as
@@ -20,6 +23,17 @@ pub struct PyType(PyAny);
2023
2124pyobject_native_type_core ! ( PyType , pyobject_native_static_type_object!( ffi:: PyType_Type ) , #checkfunction=ffi:: PyType_Check ) ;
2225
26+ #[ cfg( not( Py_LIMITED_API ) ) ]
27+ pyobject_native_type_sized ! ( PyType , ffi:: PyHeapTypeObject ) ;
28+
29+ #[ cfg( not( Py_LIMITED_API ) ) ]
30+ impl crate :: impl_:: pyclass:: PyClassBaseType for PyType {
31+ type LayoutAsBase = crate :: impl_:: pycell:: PyClassObjectBase < ffi:: PyHeapTypeObject > ;
32+ type BaseNativeType = PyType ;
33+ type Initializer = crate :: impl_:: pyclass_init:: PyNativeTypeInitializer < Self > ;
34+ type PyClassMutability = crate :: pycell:: impl_:: ImmutableClass ;
35+ }
36+
2337impl PyType {
2438 /// Creates a new type object.
2539 #[ inline]
@@ -50,6 +64,29 @@ impl PyType {
5064 . downcast_unchecked ( )
5165 . to_owned ( )
5266 }
67+
68+ /// Creates a new type object (class). The resulting type/class will inherit the given metaclass `T`
69+ ///
70+ /// Equivalent to calling `type(name, bases, dict, **kwds)`
71+ /// <https://docs.python.org/3/library/functions.html#type>
72+ #[ cfg( not( Py_LIMITED_API ) ) ]
73+ pub fn new_type < ' py , T : PyTypeInfo > (
74+ py : Python < ' py > ,
75+ args : & Bound < ' py , PyTuple > ,
76+ kwargs : Option < & Bound < ' py , PyDict > > ,
77+ ) -> PyResult < Bound < ' py , T > > {
78+ let new_fn = unsafe {
79+ ffi:: PyType_Type
80+ . tp_new
81+ . expect ( "PyType_Type.tp_new should be present" )
82+ } ;
83+ let raw_type = T :: type_object_raw ( py) ;
84+ let raw_args = args. as_ptr ( ) ;
85+ let raw_kwargs = kwargs. map ( |v| v. as_ptr ( ) ) . unwrap_or ( std:: ptr:: null_mut ( ) ) ;
86+ let obj_ptr = unsafe { new_fn ( raw_type, raw_args, raw_kwargs) } ;
87+ let borrowed_obj = unsafe { Borrowed :: from_ptr_or_err ( py, obj_ptr) } ?;
88+ Ok ( borrowed_obj. downcast ( ) ?. to_owned ( ) )
89+ }
5390}
5491
5592/// Implementation of functionality for [`PyType`].
@@ -390,4 +427,40 @@ class OuterClass:
390427 ) ;
391428 } ) ;
392429 }
430+
431+ #[ test]
432+ #[ cfg( all( not( Py_LIMITED_API ) , feature = "macros" ) ) ]
433+ fn test_new_type ( ) {
434+ use crate :: {
435+ types:: { PyDict , PyList , PyString } ,
436+ IntoPy ,
437+ } ;
438+
439+ Python :: with_gil ( |py| {
440+ #[ allow( non_snake_case) ]
441+ let ListType = py. get_type :: < PyList > ( ) ;
442+ let name = PyString :: new ( py, "MyClass" ) ;
443+ let bases = PyTuple :: new ( py, [ ListType ] ) . unwrap ( ) ;
444+ let dict = PyDict :: new ( py) ;
445+ dict. set_item ( "foo" , 123_i32 . into_py ( py) ) . unwrap ( ) ;
446+ let args = PyTuple :: new ( py, [ name. as_any ( ) , bases. as_any ( ) , dict. as_any ( ) ] ) . unwrap ( ) ;
447+ #[ allow( non_snake_case) ]
448+ let MyClass = PyType :: new_type :: < PyType > ( py, & args, None ) . unwrap ( ) ;
449+
450+ assert_eq ! ( MyClass . name( ) . unwrap( ) , "MyClass" ) ;
451+ assert_eq ! ( MyClass . qualname( ) . unwrap( ) , "MyClass" ) ;
452+
453+ crate :: py_run!(
454+ py,
455+ MyClass ,
456+ r#"
457+ assert type(MyClass) is type
458+ assert MyClass.__bases__ == (list,)
459+ assert issubclass(MyClass, list)
460+ assert MyClass.foo == 123
461+ assert not hasattr(MyClass, "__module__")
462+ "#
463+ ) ;
464+ } ) ;
465+ }
393466}
0 commit comments