diff --git a/docs/api.rst b/docs/api.rst index ec0ad0e..cf86c17 100644 --- a/docs/api.rst +++ b/docs/api.rst @@ -23,6 +23,14 @@ Latest version of the header file: `pythoncapi_compat.h `_. +Python 3.16 +----------- + +.. c:function:: PyObject* PyDict_AsFrozenDictAndClear(PyObject *dict) + + See `PyDict_AsFrozenDictAndClear() documentation `__. + + Python 3.15 ----------- diff --git a/docs/changelog.rst b/docs/changelog.rst index ca4c07f..87558a5 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -1,6 +1,7 @@ Changelog ========= +* 2026-11-14: Add ``PyDict_AsFrozenDictAndClear()`` function. * 2026-11-04: Add ``Py_SETREF()`` and ``Py_XSETREF()`` to the limited C API older than Python 3.16. * 2026-10-12: Add functions: diff --git a/pythoncapi_compat.h b/pythoncapi_compat.h index 0c6c1c2..a7a0b36 100644 --- a/pythoncapi_compat.h +++ b/pythoncapi_compat.h @@ -3154,6 +3154,43 @@ PyEval_GetFrameBuiltins(void) #endif +// Python 3.16.0a2 adds PyDict_AsFrozenDictAndClear() +#if (_Py_PACK_VERSION(3, 15) <= PY_VERSION_HEX \ + && (PY_VERSION_HEX < _Py_PACK_FULL_VERSION(3, 16, 0, 0xA, 2) \ + || defined(Py_LIMITED_API))) +static inline PyObject * +PyDict_AsFrozenDictAndClear(PyObject *dict) +{ + if (dict == NULL || !PyDict_Check(dict)) { + PyErr_BadInternalCall(); + return NULL; + } + +#ifndef Py_LIMITED_API + PyObject *res = PyFrozenDict_New(dict); +#else + // PyFrozenDict_New() is not part of the limited C API. + // Instead, get the built-in "frozendict" type and call frozendict(dict). + PyObject *builtins = PyEval_GetFrameBuiltins(); + assert(builtins != NULL); + PyObject *frozendict_type = PyMapping_GetItemString(builtins, "frozendict"); + Py_DECREF(builtins); + if (frozendict_type == NULL) { + return NULL; + } + + PyObject *res = PyObject_CallOneArg(frozendict_type, dict); + Py_DECREF(frozendict_type); +#endif + if (res == NULL) { + return NULL; + } + PyDict_Clear(dict); + return res; +} +#endif + + #ifdef __cplusplus } #endif diff --git a/tests/test_pythoncapi_compat_cext.c b/tests/test_pythoncapi_compat_cext.c index 977430f..30c7690 100644 --- a/tests/test_pythoncapi_compat_cext.c +++ b/tests/test_pythoncapi_compat_cext.c @@ -1155,6 +1155,9 @@ test_dict_api(PyObject *Py_UNUSED(module), PyObject *Py_UNUSED(args)) PyObject *invalid_dict = NULL; PyObject *get_value = NULL; int res; +#if _Py_PACK_VERSION(3, 15) <= PY_VERSION_HEX + PyObject *frozendict = NULL; +#endif // test PyDict_New() dict = PyDict_New(); @@ -1245,6 +1248,22 @@ test_dict_api(PyObject *Py_UNUSED(module), PyObject *Py_UNUSED(args)) PyErr_Clear(); assert(get_value == NULL); +#if _Py_PACK_VERSION(3, 15) <= PY_VERSION_HEX + assert(PyDict_Size(dict) == 1); + frozendict = PyDict_AsFrozenDictAndClear(dict); + assert(frozendict != NULL); +#ifndef Py_LIMITED_API + assert(PyFrozenDict_CheckExact(frozendict)); +#else + assert(Py_TYPE(frozendict) != &PyDict_Type); +#endif + assert(PyDict_GetItemRef(frozendict, key, &get_value) == 1); + assert(get_value == value); + Py_DECREF(get_value); + Py_DECREF(frozendict); + assert(PyDict_Size(dict) == 0); +#endif + Py_DECREF(dict); Py_DECREF(key); Py_DECREF(missing_key);