bpo-38787: C API for module state access from extension methods (PEP 573) (GH-19936)

Module C state is now accessible from C-defined heap type methods (PEP 573).
Patch by Marcel Plch and Petr Viktorin.

Co-authored-by: Marcel Plch <mplch@redhat.com>
Co-authored-by: Victor Stinner <vstinner@python.org>
This commit is contained in:
Petr Viktorin 2020-05-07 15:39:59 +02:00 committed by GitHub
parent 4638c64295
commit e1becf46b4
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
19 changed files with 797 additions and 51 deletions

View file

@ -4,6 +4,19 @@
#include "Python.h"
/* State for testing module state access from methods */
typedef struct {
int counter;
} meth_state;
/*[clinic input]
module _testmultiphase
class _testmultiphase.StateAccessType "StateAccessTypeObject *" "!StateAccessType"
[clinic start generated code]*/
/*[clinic end generated code: output=da39a3ee5e6b4b0d input=bab9f2fe3bd312ff]*/
/* Example objects */
typedef struct {
PyObject_HEAD
@ -14,6 +27,10 @@ typedef struct {
PyObject *integer;
} testmultiphase_state;
typedef struct {
PyObject_HEAD
} StateAccessTypeObject;
/* Example methods */
static int
@ -42,6 +59,7 @@ Example_demo(ExampleObject *self, PyObject *args)
Py_RETURN_NONE;
}
#include "clinic/_testmultiphase.c.h"
static PyMethodDef Example_methods[] = {
{"demo", (PyCFunction)Example_demo, METH_VARARGS,
@ -102,6 +120,150 @@ static PyType_Spec Example_Type_spec = {
Example_Type_slots
};
/*[clinic input]
_testmultiphase.StateAccessType.get_defining_module
cls: defining_class
Return the module of the defining class.
[clinic start generated code]*/
static PyObject *
_testmultiphase_StateAccessType_get_defining_module_impl(StateAccessTypeObject *self,
PyTypeObject *cls)
/*[clinic end generated code: output=ba2a14284a5d0921 input=946149f91cf72c0d]*/
{
PyObject *retval;
retval = PyType_GetModule(cls);
if (retval == NULL) {
return NULL;
}
Py_INCREF(retval);
return retval;
}
/*[clinic input]
_testmultiphase.StateAccessType.increment_count_clinic
cls: defining_class
/
n: int = 1
*
twice: bool = False
Add 'n' from the module-state counter.
Pass 'twice' to double that amount.
This tests Argument Clinic support for defining_class.
[clinic start generated code]*/
static PyObject *
_testmultiphase_StateAccessType_increment_count_clinic_impl(StateAccessTypeObject *self,
PyTypeObject *cls,
int n, int twice)
/*[clinic end generated code: output=3b34f86bc5473204 input=551d482e1fe0b8f5]*/
{
meth_state *m_state = PyType_GetModuleState(cls);
if (twice) {
n *= 2;
}
m_state->counter += n;
Py_RETURN_NONE;
}
PyDoc_STRVAR(_StateAccessType_decrement_count__doc__,
"decrement_count($self, /, n=1, *, twice=None)\n"
"--\n"
"\n"
"Add 'n' from the module-state counter.\n"
"Pass 'twice' to double that amount.\n"
"(This is to test both positional and keyword arguments.");
// Intentionally does not use Argument Clinic
static PyObject *
_StateAccessType_increment_count_noclinic(StateAccessTypeObject *self,
PyTypeObject *defining_class,
PyObject *const *args,
Py_ssize_t nargs,
PyObject *kwnames)
{
if (!_PyArg_CheckPositional("StateAccessTypeObject.decrement_count", nargs, 0, 1)) {
return NULL;
}
long n = 1;
if (nargs) {
n = PyLong_AsLong(args[0]);
if (PyErr_Occurred()) {
return NULL;
}
}
if (kwnames && PyTuple_Check(kwnames)) {
if (PyTuple_GET_SIZE(kwnames) > 1 ||
PyUnicode_CompareWithASCIIString(
PyTuple_GET_ITEM(kwnames, 0),
"twice"
)) {
PyErr_SetString(
PyExc_TypeError,
"decrement_count only takes 'twice' keyword argument"
);
return NULL;
}
n *= 2;
}
meth_state *m_state = PyType_GetModuleState(defining_class);
m_state->counter += n;
Py_RETURN_NONE;
}
/*[clinic input]
_testmultiphase.StateAccessType.get_count
cls: defining_class
Return the value of the module-state counter.
[clinic start generated code]*/
static PyObject *
_testmultiphase_StateAccessType_get_count_impl(StateAccessTypeObject *self,
PyTypeObject *cls)
/*[clinic end generated code: output=64600f95b499a319 input=d5d181f12384849f]*/
{
meth_state *m_state = PyType_GetModuleState(cls);
return PyLong_FromLong(m_state->counter);
}
static PyMethodDef StateAccessType_methods[] = {
_TESTMULTIPHASE_STATEACCESSTYPE_GET_DEFINING_MODULE_METHODDEF
_TESTMULTIPHASE_STATEACCESSTYPE_GET_COUNT_METHODDEF
_TESTMULTIPHASE_STATEACCESSTYPE_INCREMENT_COUNT_CLINIC_METHODDEF
{
"increment_count_noclinic",
(PyCFunction)(void(*)(void))_StateAccessType_increment_count_noclinic,
METH_METHOD|METH_FASTCALL|METH_KEYWORDS,
_StateAccessType_decrement_count__doc__
},
{NULL, NULL} /* sentinel */
};
static PyType_Slot StateAccessType_Type_slots[] = {
{Py_tp_doc, "Type for testing per-module state access from methods."},
{Py_tp_methods, StateAccessType_methods},
{0, NULL}
};
static PyType_Spec StateAccessType_spec = {
"_testimportexec.StateAccessType",
sizeof(StateAccessTypeObject),
0,
Py_TPFLAGS_DEFAULT | Py_TPFLAGS_HAVE_FINALIZE | Py_TPFLAGS_BASETYPE,
StateAccessType_Type_slots
};
/* Function of two integers returning integer */
PyDoc_STRVAR(testexport_foo_doc,
@ -193,30 +355,39 @@ static int execfunc(PyObject *m)
/* Add a custom type */
temp = PyType_FromSpec(&Example_Type_spec);
if (temp == NULL)
if (temp == NULL) {
goto fail;
if (PyModule_AddObject(m, "Example", temp) != 0)
}
if (PyModule_AddObject(m, "Example", temp) != 0) {
goto fail;
}
/* Add an exception type */
temp = PyErr_NewException("_testimportexec.error", NULL, NULL);
if (temp == NULL)
if (temp == NULL) {
goto fail;
if (PyModule_AddObject(m, "error", temp) != 0)
}
if (PyModule_AddObject(m, "error", temp) != 0) {
goto fail;
}
/* Add Str */
temp = PyType_FromSpec(&Str_Type_spec);
if (temp == NULL)
if (temp == NULL) {
goto fail;
if (PyModule_AddObject(m, "Str", temp) != 0)
}
if (PyModule_AddObject(m, "Str", temp) != 0) {
goto fail;
}
if (PyModule_AddIntConstant(m, "int_const", 1969) != 0)
if (PyModule_AddIntConstant(m, "int_const", 1969) != 0) {
goto fail;
}
if (PyModule_AddStringConstant(m, "str_const", "something different") != 0)
if (PyModule_AddStringConstant(m, "str_const", "something different") != 0) {
goto fail;
}
return 0;
fail:
@ -620,6 +791,54 @@ PyInit__testmultiphase_exec_unreported_exception(PyObject *spec)
return PyModuleDef_Init(&def_exec_unreported_exception);
}
static int
meth_state_access_exec(PyObject *m)
{
PyObject *temp;
meth_state *m_state;
m_state = PyModule_GetState(m);
if (m_state == NULL) {
return -1;
}
temp = PyType_FromModuleAndSpec(m, &StateAccessType_spec, NULL);
if (temp == NULL) {
return -1;
}
if (PyModule_AddObject(m, "StateAccessType", temp) != 0) {
return -1;
}
return 0;
}
static PyModuleDef_Slot meth_state_access_slots[] = {
{Py_mod_exec, meth_state_access_exec},
{0, NULL}
};
static PyModuleDef def_meth_state_access = {
PyModuleDef_HEAD_INIT, /* m_base */
"_testmultiphase_meth_state_access", /* m_name */
PyDoc_STR("Module testing access"
" to state from methods."),
sizeof(meth_state), /* m_size */
NULL, /* m_methods */
meth_state_access_slots, /* m_slots */
0, /* m_traverse */
0, /* m_clear */
0, /* m_free */
};
PyMODINIT_FUNC
PyInit__testmultiphase_meth_state_access(PyObject *spec)
{
return PyModuleDef_Init(&def_meth_state_access);
}
/*** Helper for imp test ***/
static PyModuleDef imp_dummy_def = TEST_MODULE_DEF("imp_dummy", main_slots, testexport_methods);

101
Modules/clinic/_testmultiphase.c.h generated Normal file
View file

@ -0,0 +1,101 @@
/*[clinic input]
preserve
[clinic start generated code]*/
PyDoc_STRVAR(_testmultiphase_StateAccessType_get_defining_module__doc__,
"get_defining_module($self, /)\n"
"--\n"
"\n"
"Return the module of the defining class.");
#define _TESTMULTIPHASE_STATEACCESSTYPE_GET_DEFINING_MODULE_METHODDEF \
{"get_defining_module", (PyCFunction)(void(*)(void))_testmultiphase_StateAccessType_get_defining_module, METH_METHOD|METH_FASTCALL|METH_KEYWORDS, _testmultiphase_StateAccessType_get_defining_module__doc__},
static PyObject *
_testmultiphase_StateAccessType_get_defining_module_impl(StateAccessTypeObject *self,
PyTypeObject *cls);
static PyObject *
_testmultiphase_StateAccessType_get_defining_module(StateAccessTypeObject *self, PyTypeObject *cls, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames)
{
PyObject *return_value = NULL;
static const char * const _keywords[] = { NULL};
static _PyArg_Parser _parser = {":get_defining_module", _keywords, 0};
if (!_PyArg_ParseStackAndKeywords(args, nargs, kwnames, &_parser
)) {
goto exit;
}
return_value = _testmultiphase_StateAccessType_get_defining_module_impl(self, cls);
exit:
return return_value;
}
PyDoc_STRVAR(_testmultiphase_StateAccessType_increment_count_clinic__doc__,
"increment_count_clinic($self, /, n=1, *, twice=False)\n"
"--\n"
"\n"
"Add \'n\' from the module-state counter.\n"
"\n"
"Pass \'twice\' to double that amount.\n"
"\n"
"This tests Argument Clinic support for defining_class.");
#define _TESTMULTIPHASE_STATEACCESSTYPE_INCREMENT_COUNT_CLINIC_METHODDEF \
{"increment_count_clinic", (PyCFunction)(void(*)(void))_testmultiphase_StateAccessType_increment_count_clinic, METH_METHOD|METH_FASTCALL|METH_KEYWORDS, _testmultiphase_StateAccessType_increment_count_clinic__doc__},
static PyObject *
_testmultiphase_StateAccessType_increment_count_clinic_impl(StateAccessTypeObject *self,
PyTypeObject *cls,
int n, int twice);
static PyObject *
_testmultiphase_StateAccessType_increment_count_clinic(StateAccessTypeObject *self, PyTypeObject *cls, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames)
{
PyObject *return_value = NULL;
static const char * const _keywords[] = {"n", "twice", NULL};
static _PyArg_Parser _parser = {"|i$p:increment_count_clinic", _keywords, 0};
int n = 1;
int twice = 0;
if (!_PyArg_ParseStackAndKeywords(args, nargs, kwnames, &_parser,
&n, &twice)) {
goto exit;
}
return_value = _testmultiphase_StateAccessType_increment_count_clinic_impl(self, cls, n, twice);
exit:
return return_value;
}
PyDoc_STRVAR(_testmultiphase_StateAccessType_get_count__doc__,
"get_count($self, /)\n"
"--\n"
"\n"
"Return the value of the module-state counter.");
#define _TESTMULTIPHASE_STATEACCESSTYPE_GET_COUNT_METHODDEF \
{"get_count", (PyCFunction)(void(*)(void))_testmultiphase_StateAccessType_get_count, METH_METHOD|METH_FASTCALL|METH_KEYWORDS, _testmultiphase_StateAccessType_get_count__doc__},
static PyObject *
_testmultiphase_StateAccessType_get_count_impl(StateAccessTypeObject *self,
PyTypeObject *cls);
static PyObject *
_testmultiphase_StateAccessType_get_count(StateAccessTypeObject *self, PyTypeObject *cls, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames)
{
PyObject *return_value = NULL;
static const char * const _keywords[] = { NULL};
static _PyArg_Parser _parser = {":get_count", _keywords, 0};
if (!_PyArg_ParseStackAndKeywords(args, nargs, kwnames, &_parser
)) {
goto exit;
}
return_value = _testmultiphase_StateAccessType_get_count_impl(self, cls);
exit:
return return_value;
}
/*[clinic end generated code: output=39eea487e94e7f5d input=a9049054013a1b77]*/