Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions docs/api.rst
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,14 @@ Latest version of the header file:
`pythoncapi_compat.h <https://raw.githubusercontent.com/python/pythoncapi-compat/main/pythoncapi_compat.h>`_.


Python 3.16
-----------

.. c:function:: PyObject* PyDict_AsFrozenDictAndClear(PyObject *dict)

See `PyDict_AsFrozenDictAndClear() documentation <https://docs.python.org/dev/c-api/dict.html#c.PyDict_AsFrozenDictAndClear>`__.


Python 3.15
-----------

Expand Down
1 change: 1 addition & 0 deletions docs/changelog.rst
Original file line number Diff line number Diff line change
@@ -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:
Expand Down
37 changes: 37 additions & 0 deletions pythoncapi_compat.h
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
19 changes: 19 additions & 0 deletions tests/test_pythoncapi_compat_cext.c
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand Down Expand Up @@ -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);
Expand Down
Loading