@@ -8,6 +8,9 @@ use crate::{ffi, Bound, PyAny, PyTypeInfo, Python};
8
8
9
9
use super :: PyString ;
10
10
11
+ #[ cfg( not( Py_LIMITED_API ) ) ]
12
+ use super :: PyDict ;
13
+
11
14
/// Represents a reference to a Python `type` object.
12
15
///
13
16
/// Values of this type are accessed via PyO3's smart pointers, e.g. as
@@ -20,6 +23,17 @@ pub struct PyType(PyAny);
20
23
21
24
pyobject_native_type_core ! ( PyType , pyobject_native_static_type_object!( ffi:: PyType_Type ) , #checkfunction=ffi:: PyType_Check ) ;
22
25
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
+
23
37
impl PyType {
24
38
/// Creates a new type object.
25
39
#[ inline]
@@ -50,6 +64,29 @@ impl PyType {
50
64
. downcast_unchecked ( )
51
65
. to_owned ( )
52
66
}
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
+ }
53
90
}
54
91
55
92
/// Implementation of functionality for [`PyType`].
@@ -390,4 +427,40 @@ class OuterClass:
390
427
) ;
391
428
} ) ;
392
429
}
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
+ }
393
466
}
0 commit comments