Skip to content

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

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 9 commits into from
May 7, 2020
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
Add tests: with clinic and without clinic
  • Loading branch information
Marcel Plch authored and encukou committed May 5, 2020
commit 3e1aa3df35372e1d0d302a47e8b806ea843f905f
73 changes: 73 additions & 0 deletions Lib/test/test_capi.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@
import time
import unittest
import weakref
import importlib.machinery
import importlib.util
from test import support
from test.support import MISSING_C_DOCSTRINGS
from test.support.script_helper import assert_python_failure, assert_python_ok
Expand Down Expand Up @@ -774,5 +776,76 @@ class PyMemDefaultTests(PyMemDebugTests):
PYTHONMALLOC = ''


class Test_ModuleStateAccess(unittest.TestCase):
"""Test access to module start (PEP 573)"""

# The C part of the tests lives in _testmultiphase, in a module called
# _testmultiphase_meth_state_access.
# This module has multi-phase initialization, unlike _testcapi.

def setUp(self):
fullname = '_testmultiphase_meth_state_access' # XXX
origin = importlib.util.find_spec('_testmultiphase').origin
loader = importlib.machinery.ExtensionFileLoader(fullname, origin)
spec = importlib.util.spec_from_loader(fullname, loader)
module = importlib.util.module_from_spec(spec)
loader.exec_module(module)
self.module = module

def test_subclass_get_module(self):
"""PyType_GetModule for defining_class"""
class StateAccessType_Subclass(self.module.StateAccessType):
pass

instance = StateAccessType_Subclass()
self.assertIs(instance.get_defining_module(), self.module)

def test_subclass_get_module_with_super(self):
class StateAccessType_Subclass(self.module.StateAccessType):
def get_defining_module(self):
return super().get_defining_module()

instance = StateAccessType_Subclass()
self.assertIs(instance.get_defining_module(), self.module)

def test_state_access(self):
"""Checks methods defined with and without argument clinic

This tests a no-arg method (get_count) and a method with
both a positional and keyword argument.
"""

a = self.module.StateAccessType()
b = self.module.StateAccessType()

methods = {
'clinic': a.increment_count_clinic,
'noclinic': a.increment_count_noclinic,
}

for name, increment_count in methods.items():
with self.subTest(name):
self.assertEqual(a.get_count(), b.get_count())
self.assertEqual(a.get_count(), 0)

increment_count()
self.assertEqual(a.get_count(), b.get_count())
self.assertEqual(a.get_count(), 1)

increment_count(3)
self.assertEqual(a.get_count(), b.get_count())
self.assertEqual(a.get_count(), 4)

increment_count(-2, twice=True)
self.assertEqual(a.get_count(), b.get_count())
self.assertEqual(a.get_count(), 0)

with self.assertRaises(TypeError):
increment_count(thrice=3)

with self.assertRaises(TypeError):
increment_count(1, 2, 3)


if __name__ == "__main__":
unittest.main()
235 changes: 227 additions & 8 deletions Modules/_testmultiphase.c
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -14,6 +27,10 @@ typedef struct {
PyObject *integer;
} testmultiphase_state;

typedef struct {
PyObject_HEAD
} StateAccessTypeObject;

/* Example methods */

static int
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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:
Expand Down Expand Up @@ -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 = {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Using designated initializers of c99 from pep7 would be better ;)

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);
Expand Down
Loading
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy