[Python-checkins] gh-94808: add tests covering `PyFunction_GetKwDefaults` and `PyFunction_SetKwDefaults` (GH-98809)

iritkatriel webhook-mailer at python.org
Sat Nov 5 13:08:52 EDT 2022


https://github.com/python/cpython/commit/317acb80387674db8c94f48bb9823ae516d05f5c
commit: 317acb80387674db8c94f48bb9823ae516d05f5c
branch: main
author: Nikita Sobolev <mail at sobolevn.me>
committer: iritkatriel <1055913+iritkatriel at users.noreply.github.com>
date: 2022-11-05T17:08:47Z
summary:

gh-94808: add tests covering `PyFunction_GetKwDefaults` and `PyFunction_SetKwDefaults` (GH-98809)

files:
M Lib/test/test_capi.py
M Modules/_testcapimodule.c

diff --git a/Lib/test/test_capi.py b/Lib/test/test_capi.py
index 0ecc6481cfce..213b6d4feb63 100644
--- a/Lib/test/test_capi.py
+++ b/Lib/test/test_capi.py
@@ -1038,7 +1038,14 @@ 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)
@@ -1046,10 +1053,17 @@ def some(pos_only='p', zero=0, optional=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)
@@ -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))
@@ -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):
 
diff --git a/Modules/_testcapimodule.c b/Modules/_testcapimodule.c
index f0f6d809cc85..66d1d476328d 100644
--- a/Modules/_testcapimodule.c
+++ b/Modules/_testcapimodule.c
@@ -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
 
@@ -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},



More information about the Python-checkins mailing list