From 25a6fbbe88fb21d7ca696b85c7aaec1c6f63dd9c Mon Sep 17 00:00:00 2001 From: sobolevn Date: Wed, 19 Oct 2022 17:51:09 +0300 Subject: [PATCH] gh-94808: cover `PyFunction_GetDefaults` and `PyFunction_SetDefaults` --- Lib/test/test_capi.py | 42 +++++++++++++++++++++++++++++++++++++++ Modules/_testcapimodule.c | 29 +++++++++++++++++++++++++++ 2 files changed, 71 insertions(+) diff --git a/Lib/test/test_capi.py b/Lib/test/test_capi.py index a2183cfb0fdf6a..68d81a53600b49 100644 --- a/Lib/test/test_capi.py +++ b/Lib/test/test_capi.py @@ -930,6 +930,48 @@ def some(): with self.assertRaises(SystemError): _testcapi.function_get_module(None) # not a function + def test_function_get_defaults(self): + def some(pos_only='p', zero=0, optional=None): + 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 + + def test_function_set_defaults(self): + def some(pos_only='p', zero=0, optional=None): + pass + + old_defaults = ('p', 0, None) + self.assertEqual(_testcapi.function_get_defaults(some), old_defaults) + self.assertEqual(some.__defaults__, old_defaults) + + with self.assertRaises(SystemError): + _testcapi.function_set_defaults(some, 1) # not tuple or None + 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) + + class tuplesub(tuple): ... # tuple subclasses must work + + new_defaults = tuplesub(((1, 2), ['a', 'b'], None)) + _testcapi.function_set_defaults(some, new_defaults) + self.assertEqual(_testcapi.function_get_defaults(some), new_defaults) + self.assertEqual(some.__defaults__, new_defaults) + + # `None` is special, it sets `defaults` to `NULL`, + # it needs special handling in `_testcapi`: + _testcapi.function_set_defaults(some, None) + self.assertEqual(_testcapi.function_get_defaults(some), None) + self.assertEqual(some.__defaults__, None) + class TestPendingCalls(unittest.TestCase): diff --git a/Modules/_testcapimodule.c b/Modules/_testcapimodule.c index 624e878b20d822..a42835d97c2c0d 100644 --- a/Modules/_testcapimodule.c +++ b/Modules/_testcapimodule.c @@ -5694,6 +5694,33 @@ function_get_module(PyObject *self, PyObject *func) } } +static PyObject * +function_get_defaults(PyObject *self, PyObject *func) +{ + PyObject *defaults = PyFunction_GetDefaults(func); + if (defaults != NULL) { + Py_INCREF(defaults); + return defaults; + } else if (PyErr_Occurred()) { + return NULL; + } else { + Py_RETURN_NONE; // This can happen when `defaults` are set to `None` + } +} + +static PyObject * +function_set_defaults(PyObject *self, PyObject *args) +{ + PyObject *func = NULL, *defaults = NULL; + if (!PyArg_ParseTuple(args, "OO", &func, &defaults)) { + return NULL; + } + int result = PyFunction_SetDefaults(func, defaults); + if (result == -1) + return NULL; + Py_RETURN_NONE; +} + static PyObject *test_buildvalue_issue38913(PyObject *, PyObject *); static PyObject *getargs_s_hash_int(PyObject *, PyObject *, PyObject*); @@ -5981,6 +6008,8 @@ static PyMethodDef TestMethods[] = { {"function_get_code", function_get_code, METH_O, NULL}, {"function_get_globals", function_get_globals, METH_O, NULL}, {"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}, {NULL, NULL} /* sentinel */ };