summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorNikita Sobolev <mail@sobolevn.me>2022-10-27 00:47:29 (GMT)
committerGitHub <noreply@github.com>2022-10-27 00:47:29 (GMT)
commit7b24333fff51f9ca36ef19748016cc2b39ab6301 (patch)
tree8a26e5b4db2cd4ca509b822b6c96baf88495ebe7
parent29b391b1378577825a658b14764a8ff3e0b5c958 (diff)
downloadcpython-7b24333fff51f9ca36ef19748016cc2b39ab6301.zip
cpython-7b24333fff51f9ca36ef19748016cc2b39ab6301.tar.gz
cpython-7b24333fff51f9ca36ef19748016cc2b39ab6301.tar.bz2
gh-94808: cover `PyFunction_GetDefaults` and `PyFunction_SetDefaults` (#98449)
-rw-r--r--Lib/test/test_capi.py42
-rw-r--r--Modules/_testcapimodule.c29
2 files changed, 71 insertions, 0 deletions
diff --git a/Lib/test/test_capi.py b/Lib/test/test_capi.py
index 9446d47..2a35576 100644
--- a/Lib/test/test_capi.py
+++ b/Lib/test/test_capi.py
@@ -942,6 +942,48 @@ class CAPITest(unittest.TestCase):
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 fdf2f20..bade0db 100644
--- a/Modules/_testcapimodule.c
+++ b/Modules/_testcapimodule.c
@@ -5788,6 +5788,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;
+}
+
// type watchers
@@ -6202,6 +6229,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},
{"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},