How to define constant values of Python type defined in C++ extension with Py_LIMITED_API?

I want to use Py_LIMITED_API. Thus, instead of defining my Python type statically, I define it dynamically, with specs and slots as follows.

    struct MyType { PyObject_HEAD };
    
    static void MyType_dealloc( MyType * self ) { PyObject_DEL( self ); }
    
    static PyObject * MyType_new( PyTypeObject * type, PyObject * args, PyObject * kwds ) {
        return (PyObject *)PyObject_NEW( MyType, type );
    }
    
    static int MyType_init( MyType * self, PyObject * args, PyObject * kwds ) { return 0; }
    
    static PyType_Slot MyType_slots[] = {
        { Py_tp_doc, (void *)PyDoc_STR("MyType objects") },
        { Py_tp_dealloc, (void *)&MyType_dealloc },
        { Py_tp_init, (void *)&MyType_init },
        { Py_tp_new, (void *)&MyType_new },
        { 0, NULL }
    };
    
    static PyType_Spec MyType_spec = {
        "MyType",
        sizeof(MyType),
        0,
        Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE,
        MyType_slots
    };
    
    static PyTypeObject * MyTypePtr = NULL;
    
    static PyModuleDef MyModule = {
        PyModuleDef_HEAD_INIT,
        "MyModule",
        "My C++ Module.",
        -1,
        NULL, NULL, NULL, NULL, NULL
    };
    
    PyMODINIT_FUNC
    PyInit_MyModule(void)
    {
        PyObject* m = PyModule_Create( &MyModule );
        if (m == NULL) return NULL;
        MyTypePtr = (PyTypeObject*)PyType_FromSpec( &MyType_spec );
        if ( MyTypePtr == NULL ) return NULL;
        if ( PyType_Ready( MyTypePtr ) < 0 ) return NULL;
        PyDict_SetItemString( MyTypePtr->tp_dict, "Normal", PyLong_FromLong( 0 ) );
        PyDict_SetItemString( MyTypePtr->tp_dict, "Custom", PyLong_FromLong( 15 ) );
        Py_INCREF( MyTypePtr );
        PyModule_AddObject( m, "MyType", (PyObject *)MyTypePtr );
        return m;
    }

With this definition of my module, I can write the following.

    from MyModule import *
    print( MyType.Normal )
    print( MyType.Custom )

My problem is that with Py_LIMITED_API, I cannot use tp_dict to define the constant values Normal and Custom.

I’ve tried to use PyType_GetSlot, but Py_tp_dict does not exist. (I guess I understand why: it’s because it would allow modifications of existing types.)

Now my question is: In the code above with specs and slots, how can I define the constants Normal and Custom of the type MyType without using tp_dict (or anything forbidden by Py_LIMITED_API)?

You can use Py_tp_getset and PyGetSetDef instead to define a property. If you don’t provide a setter (give it NULL instead of a function pointer), it’ll be read-only.

Thanks Matthew.
However, wouldn’t these get/set definitions define instance properties where I want class properties?

Definitively, it’s not the same thing.

With tp_dict, I have to use MyType.Normal.
With getset, I have to use MyType().Normal.

It means that with getset I have to create an object (an instance) to access its property Normal.

This is not what I used to have when using tp_dict.

There’s a very simple answer: call PyObject_SetAttrString on the type object.

PyMODINIT_FUNC
PyInit_MyModule(void)
{
    PyObject* m = PyModule_Create( &MyModule );
    if (m == NULL) return NULL;
    MyTypePtr = (PyTypeObject*)PyType_FromSpec( &MyType_spec );
    if ( MyTypePtr == NULL ) return NULL;
    if ( PyType_Ready( MyTypePtr ) < 0 ) return NULL;
    PyObject_SetAttrString( (PyObject*)MyTypePtr, "Normal", PyLong_FromLong( 0 ) );
    PyObject_SetAttrString( (PyObject*)MyTypePtr, "Custom", PyLong_FromLong( 15 ) );
    Py_INCREF( MyTypePtr );
    PyModule_AddObject( m, "MyType", (PyObject *)MyTypePtr );
    return m;
}