Skip to content

Commit

Permalink
gh-94808: add tests covering PyFunction_GetKwDefaults and `PyFuncti…
Browse files Browse the repository at this point in the history
…on_SetKwDefaults` (GH-98809)
  • Loading branch information
sobolevn authored Nov 5, 2022
1 parent c5c4077 commit 317acb8
Show file tree
Hide file tree
Showing 2 changed files with 124 additions and 3 deletions.
98 changes: 95 additions & 3 deletions Lib/test/test_capi.py
Original file line number Diff line number Diff line change
Expand Up @@ -1038,18 +1038,32 @@ def some():
_testcapi.function_get_module(None) # not a function

def test_function_get_defaults(self):
def some(pos_only='p', zero=0, optional=None):
def some(
pos_only1, pos_only2='p',
/,
zero=0, optional=None,
*,
kw1,
kw2=True,
):
pass

defaults = _testcapi.function_get_defaults(some)
self.assertEqual(defaults, ('p', 0, None))
self.assertEqual(defaults, some.__defaults__)

with self.assertRaises(SystemError):
_testcapi.function_get_module(None) # not a function
_testcapi.function_get_defaults(None) # not a function

def test_function_set_defaults(self):
def some(pos_only='p', zero=0, optional=None):
def some(
pos_only1, pos_only2='p',
/,
zero=0, optional=None,
*,
kw1,
kw2=True,
):
pass

old_defaults = ('p', 0, None)
Expand All @@ -1061,11 +1075,22 @@ def some(pos_only='p', zero=0, optional=None):
self.assertEqual(_testcapi.function_get_defaults(some), old_defaults)
self.assertEqual(some.__defaults__, old_defaults)

with self.assertRaises(SystemError):
_testcapi.function_set_defaults(1, ()) # not a function
self.assertEqual(_testcapi.function_get_defaults(some), old_defaults)
self.assertEqual(some.__defaults__, old_defaults)

new_defaults = ('q', 1, None)
_testcapi.function_set_defaults(some, new_defaults)
self.assertEqual(_testcapi.function_get_defaults(some), new_defaults)
self.assertEqual(some.__defaults__, new_defaults)

# Empty tuple is fine:
new_defaults = ()
_testcapi.function_set_defaults(some, new_defaults)
self.assertEqual(_testcapi.function_get_defaults(some), new_defaults)
self.assertEqual(some.__defaults__, new_defaults)

class tuplesub(tuple): ... # tuple subclasses must work

new_defaults = tuplesub(((1, 2), ['a', 'b'], None))
Expand All @@ -1079,6 +1104,73 @@ class tuplesub(tuple): ... # tuple subclasses must work
self.assertEqual(_testcapi.function_get_defaults(some), None)
self.assertEqual(some.__defaults__, None)

def test_function_get_kw_defaults(self):
def some(
pos_only1, pos_only2='p',
/,
zero=0, optional=None,
*,
kw1,
kw2=True,
):
pass

defaults = _testcapi.function_get_kw_defaults(some)
self.assertEqual(defaults, {'kw2': True})
self.assertEqual(defaults, some.__kwdefaults__)

with self.assertRaises(SystemError):
_testcapi.function_get_kw_defaults(None) # not a function

def test_function_set_kw_defaults(self):
def some(
pos_only1, pos_only2='p',
/,
zero=0, optional=None,
*,
kw1,
kw2=True,
):
pass

old_defaults = {'kw2': True}
self.assertEqual(_testcapi.function_get_kw_defaults(some), old_defaults)
self.assertEqual(some.__kwdefaults__, old_defaults)

with self.assertRaises(SystemError):
_testcapi.function_set_kw_defaults(some, 1) # not dict or None
self.assertEqual(_testcapi.function_get_kw_defaults(some), old_defaults)
self.assertEqual(some.__kwdefaults__, old_defaults)

with self.assertRaises(SystemError):
_testcapi.function_set_kw_defaults(1, {}) # not a function
self.assertEqual(_testcapi.function_get_kw_defaults(some), old_defaults)
self.assertEqual(some.__kwdefaults__, old_defaults)

new_defaults = {'kw2': (1, 2, 3)}
_testcapi.function_set_kw_defaults(some, new_defaults)
self.assertEqual(_testcapi.function_get_kw_defaults(some), new_defaults)
self.assertEqual(some.__kwdefaults__, new_defaults)

# Empty dict is fine:
new_defaults = {}
_testcapi.function_set_kw_defaults(some, new_defaults)
self.assertEqual(_testcapi.function_get_kw_defaults(some), new_defaults)
self.assertEqual(some.__kwdefaults__, new_defaults)

class dictsub(dict): ... # dict subclasses must work

new_defaults = dictsub({'kw2': None})
_testcapi.function_set_kw_defaults(some, new_defaults)
self.assertEqual(_testcapi.function_get_kw_defaults(some), new_defaults)
self.assertEqual(some.__kwdefaults__, new_defaults)

# `None` is special, it sets `kwdefaults` to `NULL`,
# it needs special handling in `_testcapi`:
_testcapi.function_set_kw_defaults(some, None)
self.assertEqual(_testcapi.function_get_kw_defaults(some), None)
self.assertEqual(some.__kwdefaults__, None)


class TestPendingCalls(unittest.TestCase):

Expand Down
29 changes: 29 additions & 0 deletions Modules/_testcapimodule.c
Original file line number Diff line number Diff line change
Expand Up @@ -5862,6 +5862,33 @@ function_set_defaults(PyObject *self, PyObject *args)
Py_RETURN_NONE;
}

static PyObject *
function_get_kw_defaults(PyObject *self, PyObject *func)
{
PyObject *defaults = PyFunction_GetKwDefaults(func);
if (defaults != NULL) {
Py_INCREF(defaults);
return defaults;
} else if (PyErr_Occurred()) {
return NULL;
} else {
Py_RETURN_NONE; // This can happen when `kwdefaults` are set to `None`
}
}

static PyObject *
function_set_kw_defaults(PyObject *self, PyObject *args)
{
PyObject *func = NULL, *defaults = NULL;
if (!PyArg_ParseTuple(args, "OO", &func, &defaults)) {
return NULL;
}
int result = PyFunction_SetKwDefaults(func, defaults);
if (result == -1)
return NULL;
Py_RETURN_NONE;
}


// type watchers

Expand Down Expand Up @@ -6281,6 +6308,8 @@ static PyMethodDef TestMethods[] = {
{"function_get_module", function_get_module, METH_O, NULL},
{"function_get_defaults", function_get_defaults, METH_O, NULL},
{"function_set_defaults", function_set_defaults, METH_VARARGS, NULL},
{"function_get_kw_defaults", function_get_kw_defaults, METH_O, NULL},
{"function_set_kw_defaults", function_set_kw_defaults, METH_VARARGS, NULL},
{"add_type_watcher", add_type_watcher, METH_O, NULL},
{"clear_type_watcher", clear_type_watcher, METH_O, NULL},
{"watch_type", watch_type, METH_VARARGS, NULL},
Expand Down

0 comments on commit 317acb8

Please sign in to comment.