Skip to content

Commit

Permalink
gh-106004: Add PyDict_GetItemRef() function (#106005)
Browse files Browse the repository at this point in the history
* Add PyDict_GetItemRef() and PyDict_GetItemStringRef() functions.
  Add these functions to the stable ABI version 3.13.
* Add unit tests on the PyDict C API in test_capi.
  • Loading branch information
vstinner authored Jul 21, 2023
1 parent 0ba07b2 commit 41ca164
Show file tree
Hide file tree
Showing 10 changed files with 308 additions and 18 deletions.
28 changes: 26 additions & 2 deletions Doc/c-api/dict.rst
Original file line number Diff line number Diff line change
Expand Up @@ -93,10 +93,26 @@ Dictionary Objects
Return ``0`` on success or ``-1`` on failure.
.. c:function:: int PyDict_GetItemRef(PyObject *p, PyObject *key, PyObject **result)
Return a new :term:`strong reference` to the object from dictionary *p*
which has a key *key*:
* If the key is present, set *\*result* to a new :term:`strong reference`
to the value and return ``1``.
* If the key is missing, set *\*result* to ``NULL`` and return ``0``.
* On error, raise an exception and return ``-1``.
.. versionadded:: 3.13
See also the :c:func:`PyObject_GetItem` function.
.. c:function:: PyObject* PyDict_GetItem(PyObject *p, PyObject *key)
Return the object from dictionary *p* which has a key *key*. Return ``NULL``
if the key *key* is not present, but *without* setting an exception.
Return a :term:`borrowed reference` to the object from dictionary *p* which
has a key *key*. Return ``NULL`` if the key *key* is missing *without*
setting an exception.
.. note::
Expand Down Expand Up @@ -131,6 +147,14 @@ Dictionary Objects
:c:func:`PyUnicode_FromString` *key* instead.
.. c:function:: int PyDict_GetItemStringRef(PyObject *p, const char *key, PyObject **result)
Similar than :c:func:`PyDict_GetItemRef`, but *key* is specified as a
:c:expr:`const char*`, rather than a :c:expr:`PyObject*`.
.. versionadded:: 3.13
.. c:function:: PyObject* PyDict_SetDefault(PyObject *p, PyObject *key, PyObject *defaultobj)
This is the same as the Python-level :meth:`dict.setdefault`. If present, it
Expand Down
2 changes: 2 additions & 0 deletions Doc/data/stable_abi.dat

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

7 changes: 7 additions & 0 deletions Doc/whatsnew/3.13.rst
Original file line number Diff line number Diff line change
Expand Up @@ -786,6 +786,13 @@ New Features
always steals a reference to the value.
(Contributed by Serhiy Storchaka in :gh:`86493`.)

* Added :c:func:`PyDict_GetItemRef` and :c:func:`PyDict_GetItemStringRef`
functions: similar to :c:func:`PyDict_GetItemWithError` but returning a
:term:`strong reference` instead of a :term:`borrowed reference`. Moreover,
these functions return -1 on error and so checking ``PyErr_Occurred()`` is
not needed.
(Contributed by Victor Stinner in :gh:`106004`.)

Porting to Python 3.13
----------------------

Expand Down
9 changes: 9 additions & 0 deletions Include/dictobject.h
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,15 @@ PyAPI_FUNC(int) PyDict_MergeFromSeq2(PyObject *d,
PyAPI_FUNC(PyObject *) PyDict_GetItemString(PyObject *dp, const char *key);
PyAPI_FUNC(int) PyDict_SetItemString(PyObject *dp, const char *key, PyObject *item);
PyAPI_FUNC(int) PyDict_DelItemString(PyObject *dp, const char *key);

// Return the object from dictionary *op* which has a key *key*.
// - If the key is present, set *result to a new strong reference to the value
// and return 1.
// - If the key is missing, set *result to NULL and return 0 .
// - On error, raise an exception and return -1.
PyAPI_FUNC(int) PyDict_GetItemRef(PyObject *mp, PyObject *key, PyObject **result);
PyAPI_FUNC(int) PyDict_GetItemStringRef(PyObject *mp, const char *key, PyObject **result);

#if !defined(Py_LIMITED_API) || Py_LIMITED_API+0 >= 0x030A0000
PyAPI_FUNC(PyObject *) PyObject_GenericGetDict(PyObject *, void *);
#endif
Expand Down
2 changes: 2 additions & 0 deletions Lib/test/test_stable_abi_ctypes.py

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
Adds :c:func:`PyDict_GetItemRef` and :c:func:`PyDict_GetItemStringRef`
functions: similar to :c:func:`PyDict_GetItemWithError` but returning a
:term:`strong reference` instead of a :term:`borrowed reference`. Patch by
Victor Stinner.
4 changes: 4 additions & 0 deletions Misc/stable_abi.toml
Original file line number Diff line number Diff line change
Expand Up @@ -2446,3 +2446,7 @@
added = '3.13'
[function.PyModule_Add]
added = '3.13'
[function.PyDict_GetItemRef]
added = '3.13'
[function.PyDict_GetItemStringRef]
added = '3.13'
191 changes: 191 additions & 0 deletions Modules/_testcapimodule.c
Original file line number Diff line number Diff line change
Expand Up @@ -3464,6 +3464,196 @@ test_weakref_capi(PyObject *Py_UNUSED(module), PyObject *Py_UNUSED(args))
}


static PyObject *
test_dict_capi(PyObject *Py_UNUSED(module), PyObject *Py_UNUSED(args))
{
assert(!PyErr_Occurred());

PyObject *dict= NULL, *key = NULL, *missing_key = NULL, *value = NULL;
PyObject *invalid_key = NULL;
int res;

// test PyDict_New()
dict = PyDict_New();
if (dict == NULL) {
goto error;
}

key = PyUnicode_FromString("key");
if (key == NULL) {
goto error;
}

missing_key = PyUnicode_FromString("missing_key");
if (missing_key == NULL) {
goto error;
}

value = PyUnicode_FromString("value");
if (value == NULL) {
goto error;
}

// test PyDict_SetItem()
Py_ssize_t key_refcnt = Py_REFCNT(key);
Py_ssize_t value_refcnt = Py_REFCNT(value);
res = PyDict_SetItem(dict, key, value);
if (res < 0) {
goto error;
}
assert(res == 0);
assert(Py_REFCNT(key) == (key_refcnt + 1));
assert(Py_REFCNT(value) == (value_refcnt + 1));

// test PyDict_SetItemString()
res = PyDict_SetItemString(dict, "key", value);
if (res < 0) {
goto error;
}
assert(res == 0);
assert(Py_REFCNT(key) == (key_refcnt + 1));
assert(Py_REFCNT(value) == (value_refcnt + 1));

// test PyDict_Size()
assert(PyDict_Size(dict) == 1);

// test PyDict_Contains(), key is present
assert(PyDict_Contains(dict, key) == 1);

// test PyDict_GetItem(), key is present
assert(PyDict_GetItem(dict, key) == value);

// test PyDict_GetItemString(), key is present
assert(PyDict_GetItemString(dict, "key") == value);

// test PyDict_GetItemWithError(), key is present
assert(PyDict_GetItemWithError(dict, key) == value);
assert(!PyErr_Occurred());

// test PyDict_GetItemRef(), key is present
PyObject *get_value = Py_Ellipsis; // marker value
assert(PyDict_GetItemRef(dict, key, &get_value) == 1);
assert(get_value == value);
Py_DECREF(get_value);

// test PyDict_GetItemStringRef(), key is present
get_value = Py_Ellipsis; // marker value
assert(PyDict_GetItemStringRef(dict, "key", &get_value) == 1);
assert(get_value == value);
Py_DECREF(get_value);

// test PyDict_Contains(), missing key
assert(PyDict_Contains(dict, missing_key) == 0);

// test PyDict_GetItem(), missing key
assert(PyDict_GetItem(dict, missing_key) == NULL);
assert(!PyErr_Occurred());

// test PyDict_GetItemString(), missing key
assert(PyDict_GetItemString(dict, "missing_key") == NULL);
assert(!PyErr_Occurred());

// test PyDict_GetItemWithError(), missing key
assert(PyDict_GetItem(dict, missing_key) == NULL);
assert(!PyErr_Occurred());

// test PyDict_GetItemRef(), missing key
get_value = Py_Ellipsis; // marker value
assert(PyDict_GetItemRef(dict, missing_key, &get_value) == 0);
assert(!PyErr_Occurred());
assert(get_value == NULL);

// test PyDict_GetItemStringRef(), missing key
get_value = Py_Ellipsis; // marker value
assert(PyDict_GetItemStringRef(dict, "missing_key", &get_value) == 0);
assert(!PyErr_Occurred());
assert(get_value == NULL);

// test PyDict_GetItem(), invalid dict
PyObject *invalid_dict = key; // borrowed reference
assert(PyDict_GetItem(invalid_dict, key) == NULL);
assert(!PyErr_Occurred());

// test PyDict_GetItemWithError(), invalid dict
assert(PyDict_GetItemWithError(invalid_dict, key) == NULL);
assert(PyErr_ExceptionMatches(PyExc_SystemError));
PyErr_Clear();

// test PyDict_GetItemRef(), invalid dict
get_value = Py_Ellipsis; // marker value
assert(PyDict_GetItemRef(invalid_dict, key, &get_value) == -1);
assert(PyErr_ExceptionMatches(PyExc_SystemError));
PyErr_Clear();
assert(get_value == NULL);

// test PyDict_GetItemStringRef(), invalid dict
get_value = Py_Ellipsis; // marker value
assert(PyDict_GetItemStringRef(invalid_dict, "key", &get_value) == -1);
assert(PyErr_ExceptionMatches(PyExc_SystemError));
PyErr_Clear();
assert(get_value == NULL);

invalid_key = PyList_New(0);
if (invalid_key == NULL) {
goto error;
}

// test PyDict_Contains(), invalid key
assert(PyDict_Contains(dict, invalid_key) == -1);
assert(PyErr_ExceptionMatches(PyExc_TypeError));
PyErr_Clear();

// test PyDict_GetItem(), invalid key
assert(PyDict_GetItem(dict, invalid_key) == NULL);
assert(!PyErr_Occurred());

// test PyDict_GetItemWithError(), invalid key
assert(PyDict_GetItemWithError(dict, invalid_key) == NULL);
assert(PyErr_ExceptionMatches(PyExc_TypeError));
PyErr_Clear();

// test PyDict_GetItemRef(), invalid key
get_value = Py_Ellipsis; // marker value
assert(PyDict_GetItemRef(dict, invalid_key, &get_value) == -1);
assert(PyErr_ExceptionMatches(PyExc_TypeError));
PyErr_Clear();
assert(get_value == NULL);

// test PyDict_DelItem(), key is present
assert(PyDict_DelItem(dict, key) == 0);
assert(PyDict_Size(dict) == 0);

// test PyDict_DelItem(), missing key
assert(PyDict_DelItem(dict, missing_key) == -1);
assert(PyErr_ExceptionMatches(PyExc_KeyError));
PyErr_Clear();

// test PyDict_DelItem(), invalid key
assert(PyDict_DelItem(dict, invalid_key) == -1);
assert(PyErr_ExceptionMatches(PyExc_TypeError));
PyErr_Clear();

// test PyDict_Clear()
PyDict_Clear(dict);

Py_DECREF(dict);
Py_DECREF(key);
Py_DECREF(missing_key);
Py_DECREF(value);
Py_DECREF(invalid_key);

Py_RETURN_NONE;

error:
Py_XDECREF(dict);
Py_XDECREF(key);
Py_XDECREF(missing_key);
Py_XDECREF(value);
Py_XDECREF(invalid_key);
return NULL;
}


static PyMethodDef TestMethods[] = {
{"set_errno", set_errno, METH_VARARGS},
{"test_config", test_config, METH_NOARGS},
Expand Down Expand Up @@ -3609,6 +3799,7 @@ static PyMethodDef TestMethods[] = {
{"function_set_kw_defaults", function_set_kw_defaults, METH_VARARGS, NULL},
{"check_pyimport_addmodule", check_pyimport_addmodule, METH_VARARGS},
{"test_weakref_capi", test_weakref_capi, METH_NOARGS},
{"test_dict_capi", test_dict_capi, METH_NOARGS},
{NULL, NULL} /* sentinel */
};

Expand Down
Loading

0 comments on commit 41ca164

Please sign in to comment.