Skip to content

Commit a60ab95

Browse files
committed
gh-106004: Add PyDict_GetItemRef() function
* 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.
1 parent e4b88c1 commit a60ab95

File tree

10 files changed

+308
-18
lines changed

10 files changed

+308
-18
lines changed

Doc/c-api/dict.rst

+26-2
Original file line numberDiff line numberDiff line change
@@ -93,10 +93,26 @@ Dictionary Objects
9393
Return ``0`` on success or ``-1`` on failure.
9494
9595
96+
.. c:function:: int PyDict_GetItemRef(PyObject *p, PyObject *key, PyObject **result)
97+
98+
Return a new :term:`strong reference` to the object from dictionary *p*
99+
which has a key *key*:
100+
101+
* If the key is present, set *\*result* to a new :term:`strong reference`
102+
to the value and return ``1``.
103+
* If the key is missing, set *\*result* to ``NULL`` and return ``0``.
104+
* On error, raise an exception and return ``-1``.
105+
106+
.. versionadded:: 3.13
107+
108+
See also the :c:func:`PyObject_GetItem` function.
109+
110+
96111
.. c:function:: PyObject* PyDict_GetItem(PyObject *p, PyObject *key)
97112
98-
Return the object from dictionary *p* which has a key *key*. Return ``NULL``
99-
if the key *key* is not present, but *without* setting an exception.
113+
Return a :term:`borrowed reference` to the object from dictionary *p* which
114+
has a key *key*. Return ``NULL`` if the key *key* is missing *without*
115+
setting an exception.
100116
101117
.. note::
102118
@@ -131,6 +147,14 @@ Dictionary Objects
131147
:c:func:`PyUnicode_FromString` *key* instead.
132148
133149
150+
.. c:function:: int PyDict_GetItemStringRef(PyObject *p, const char *key, PyObject **result)
151+
152+
Similar than :c:func:`PyDict_GetItemRef`, but *key* is specified as a
153+
:c:expr:`const char*`, rather than a :c:expr:`PyObject*`.
154+
155+
.. versionadded:: 3.13
156+
157+
134158
.. c:function:: PyObject* PyDict_SetDefault(PyObject *p, PyObject *key, PyObject *defaultobj)
135159
136160
This is the same as the Python-level :meth:`dict.setdefault`. If present, it

Doc/data/stable_abi.dat

+2
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Doc/whatsnew/3.13.rst

+7
Original file line numberDiff line numberDiff line change
@@ -763,6 +763,13 @@ New Features
763763
If the assertion fails, make sure that the size is set before.
764764
(Contributed by Victor Stinner in :gh:`106168`.)
765765

766+
* Added :c:func:`PyDict_GetItemRef` and :c:func:`PyDict_GetItemStringRef`
767+
functions: similar to :c:func:`PyDict_GetItemWithError` but returning a
768+
:term:`strong reference` instead of a :term:`borrowed reference`. Moreover,
769+
these functions return -1 on error and so checking ``PyErr_Occurred()`` is
770+
not needed.
771+
(Contributed by Victor Stinner in :gh:`106004`.)
772+
766773
Porting to Python 3.13
767774
----------------------
768775

Include/dictobject.h

+9
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,15 @@ PyAPI_FUNC(int) PyDict_MergeFromSeq2(PyObject *d,
5757
PyAPI_FUNC(PyObject *) PyDict_GetItemString(PyObject *dp, const char *key);
5858
PyAPI_FUNC(int) PyDict_SetItemString(PyObject *dp, const char *key, PyObject *item);
5959
PyAPI_FUNC(int) PyDict_DelItemString(PyObject *dp, const char *key);
60+
61+
// Return the object from dictionary *op* which has a key *key*.
62+
// - If the key is present, set *result to a new strong reference to the value
63+
// and return 1.
64+
// - If the key is missing, set *result to NULL and return 0 .
65+
// - On error, raise an exception and return -1.
66+
PyAPI_FUNC(int) PyDict_GetItemRef(PyObject *mp, PyObject *key, PyObject **result);
67+
PyAPI_FUNC(int) PyDict_GetItemStringRef(PyObject *mp, const char *key, PyObject **result);
68+
6069
#if !defined(Py_LIMITED_API) || Py_LIMITED_API+0 >= 0x030A0000
6170
PyAPI_FUNC(PyObject *) PyObject_GenericGetDict(PyObject *, void *);
6271
#endif

Lib/test/test_stable_abi_ctypes.py

+2
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
Adds :c:func:`PyDict_GetItemRef` and :c:func:`PyDict_GetItemStringRef`
2+
functions: similar to :c:func:`PyDict_GetItemWithError` but returning a
3+
:term:`strong reference` instead of a :term:`borrowed reference`. Patch by
4+
Victor Stinner.

Misc/stable_abi.toml

+4
Original file line numberDiff line numberDiff line change
@@ -2444,3 +2444,7 @@
24442444
added = '3.13'
24452445
[function.PyMapping_GetOptionalItemString]
24462446
added = '3.13'
2447+
[function.PyDict_GetItemRef]
2448+
added = '3.13'
2449+
[function.PyDict_GetItemStringRef]
2450+
added = '3.13'

Modules/_testcapimodule.c

+191
Original file line numberDiff line numberDiff line change
@@ -3464,6 +3464,196 @@ test_weakref_capi(PyObject *Py_UNUSED(module), PyObject *Py_UNUSED(args))
34643464
}
34653465

34663466

3467+
static PyObject *
3468+
test_dict_capi(PyObject *Py_UNUSED(module), PyObject *Py_UNUSED(args))
3469+
{
3470+
assert(!PyErr_Occurred());
3471+
3472+
PyObject *dict= NULL, *key = NULL, *missing_key = NULL, *value = NULL;
3473+
PyObject *invalid_key = NULL;
3474+
int res;
3475+
3476+
// test PyDict_New()
3477+
dict = PyDict_New();
3478+
if (dict == NULL) {
3479+
goto error;
3480+
}
3481+
3482+
key = PyUnicode_FromString("key");
3483+
if (key == NULL) {
3484+
goto error;
3485+
}
3486+
3487+
missing_key = PyUnicode_FromString("missing_key");
3488+
if (missing_key == NULL) {
3489+
goto error;
3490+
}
3491+
3492+
value = PyUnicode_FromString("value");
3493+
if (value == NULL) {
3494+
goto error;
3495+
}
3496+
3497+
// test PyDict_SetItem()
3498+
Py_ssize_t key_refcnt = Py_REFCNT(key);
3499+
Py_ssize_t value_refcnt = Py_REFCNT(value);
3500+
res = PyDict_SetItem(dict, key, value);
3501+
if (res < 0) {
3502+
goto error;
3503+
}
3504+
assert(res == 0);
3505+
assert(Py_REFCNT(key) == (key_refcnt + 1));
3506+
assert(Py_REFCNT(value) == (value_refcnt + 1));
3507+
3508+
// test PyDict_SetItemString()
3509+
res = PyDict_SetItemString(dict, "key", value);
3510+
if (res < 0) {
3511+
goto error;
3512+
}
3513+
assert(res == 0);
3514+
assert(Py_REFCNT(key) == (key_refcnt + 1));
3515+
assert(Py_REFCNT(value) == (value_refcnt + 1));
3516+
3517+
// test PyDict_Size()
3518+
assert(PyDict_Size(dict) == 1);
3519+
3520+
// test PyDict_Contains(), key is present
3521+
assert(PyDict_Contains(dict, key) == 1);
3522+
3523+
// test PyDict_GetItem(), key is present
3524+
assert(PyDict_GetItem(dict, key) == value);
3525+
3526+
// test PyDict_GetItemString(), key is present
3527+
assert(PyDict_GetItemString(dict, "key") == value);
3528+
3529+
// test PyDict_GetItemWithError(), key is present
3530+
assert(PyDict_GetItemWithError(dict, key) == value);
3531+
assert(!PyErr_Occurred());
3532+
3533+
// test PyDict_GetItemRef(), key is present
3534+
PyObject *get_value = Py_Ellipsis; // marker value
3535+
assert(PyDict_GetItemRef(dict, key, &get_value) == 1);
3536+
assert(get_value == value);
3537+
Py_DECREF(get_value);
3538+
3539+
// test PyDict_GetItemStringRef(), key is present
3540+
get_value = Py_Ellipsis; // marker value
3541+
assert(PyDict_GetItemStringRef(dict, "key", &get_value) == 1);
3542+
assert(get_value == value);
3543+
Py_DECREF(get_value);
3544+
3545+
// test PyDict_Contains(), missing key
3546+
assert(PyDict_Contains(dict, missing_key) == 0);
3547+
3548+
// test PyDict_GetItem(), missing key
3549+
assert(PyDict_GetItem(dict, missing_key) == NULL);
3550+
assert(!PyErr_Occurred());
3551+
3552+
// test PyDict_GetItemString(), missing key
3553+
assert(PyDict_GetItemString(dict, "missing_key") == NULL);
3554+
assert(!PyErr_Occurred());
3555+
3556+
// test PyDict_GetItemWithError(), missing key
3557+
assert(PyDict_GetItem(dict, missing_key) == NULL);
3558+
assert(!PyErr_Occurred());
3559+
3560+
// test PyDict_GetItemRef(), missing key
3561+
get_value = Py_Ellipsis; // marker value
3562+
assert(PyDict_GetItemRef(dict, missing_key, &get_value) == 0);
3563+
assert(!PyErr_Occurred());
3564+
assert(get_value == NULL);
3565+
3566+
// test PyDict_GetItemStringRef(), missing key
3567+
get_value = Py_Ellipsis; // marker value
3568+
assert(PyDict_GetItemStringRef(dict, "missing_key", &get_value) == 0);
3569+
assert(!PyErr_Occurred());
3570+
assert(get_value == NULL);
3571+
3572+
// test PyDict_GetItem(), invalid dict
3573+
PyObject *invalid_dict = key; // borrowed reference
3574+
assert(PyDict_GetItem(invalid_dict, key) == NULL);
3575+
assert(!PyErr_Occurred());
3576+
3577+
// test PyDict_GetItemWithError(), invalid dict
3578+
assert(PyDict_GetItemWithError(invalid_dict, key) == NULL);
3579+
assert(PyErr_ExceptionMatches(PyExc_SystemError));
3580+
PyErr_Clear();
3581+
3582+
// test PyDict_GetItemRef(), invalid dict
3583+
get_value = Py_Ellipsis; // marker value
3584+
assert(PyDict_GetItemRef(invalid_dict, key, &get_value) == -1);
3585+
assert(PyErr_ExceptionMatches(PyExc_SystemError));
3586+
PyErr_Clear();
3587+
assert(get_value == NULL);
3588+
3589+
// test PyDict_GetItemStringRef(), invalid dict
3590+
get_value = Py_Ellipsis; // marker value
3591+
assert(PyDict_GetItemStringRef(invalid_dict, "key", &get_value) == -1);
3592+
assert(PyErr_ExceptionMatches(PyExc_SystemError));
3593+
PyErr_Clear();
3594+
assert(get_value == NULL);
3595+
3596+
invalid_key = PyList_New(0);
3597+
if (invalid_key == NULL) {
3598+
goto error;
3599+
}
3600+
3601+
// test PyDict_Contains(), invalid key
3602+
assert(PyDict_Contains(dict, invalid_key) == -1);
3603+
assert(PyErr_ExceptionMatches(PyExc_TypeError));
3604+
PyErr_Clear();
3605+
3606+
// test PyDict_GetItem(), invalid key
3607+
assert(PyDict_GetItem(dict, invalid_key) == NULL);
3608+
assert(!PyErr_Occurred());
3609+
3610+
// test PyDict_GetItemWithError(), invalid key
3611+
assert(PyDict_GetItemWithError(dict, invalid_key) == NULL);
3612+
assert(PyErr_ExceptionMatches(PyExc_TypeError));
3613+
PyErr_Clear();
3614+
3615+
// test PyDict_GetItemRef(), invalid key
3616+
get_value = Py_Ellipsis; // marker value
3617+
assert(PyDict_GetItemRef(dict, invalid_key, &get_value) == -1);
3618+
assert(PyErr_ExceptionMatches(PyExc_TypeError));
3619+
PyErr_Clear();
3620+
assert(get_value == NULL);
3621+
3622+
// test PyDict_DelItem(), key is present
3623+
assert(PyDict_DelItem(dict, key) == 0);
3624+
assert(PyDict_Size(dict) == 0);
3625+
3626+
// test PyDict_DelItem(), missing key
3627+
assert(PyDict_DelItem(dict, missing_key) == -1);
3628+
assert(PyErr_ExceptionMatches(PyExc_KeyError));
3629+
PyErr_Clear();
3630+
3631+
// test PyDict_DelItem(), invalid key
3632+
assert(PyDict_DelItem(dict, invalid_key) == -1);
3633+
assert(PyErr_ExceptionMatches(PyExc_TypeError));
3634+
PyErr_Clear();
3635+
3636+
// test PyDict_Clear()
3637+
PyDict_Clear(dict);
3638+
3639+
Py_DECREF(dict);
3640+
Py_DECREF(key);
3641+
Py_DECREF(missing_key);
3642+
Py_DECREF(value);
3643+
Py_DECREF(invalid_key);
3644+
3645+
Py_RETURN_NONE;
3646+
3647+
error:
3648+
Py_XDECREF(dict);
3649+
Py_XDECREF(key);
3650+
Py_XDECREF(missing_key);
3651+
Py_XDECREF(value);
3652+
Py_XDECREF(invalid_key);
3653+
return NULL;
3654+
}
3655+
3656+
34673657
static PyMethodDef TestMethods[] = {
34683658
{"set_errno", set_errno, METH_VARARGS},
34693659
{"test_config", test_config, METH_NOARGS},
@@ -3609,6 +3799,7 @@ static PyMethodDef TestMethods[] = {
36093799
{"function_set_kw_defaults", function_set_kw_defaults, METH_VARARGS, NULL},
36103800
{"check_pyimport_addmodule", check_pyimport_addmodule, METH_VARARGS},
36113801
{"test_weakref_capi", test_weakref_capi, METH_NOARGS},
3802+
{"test_dict_capi", test_dict_capi, METH_NOARGS},
36123803
{NULL, NULL} /* sentinel */
36133804
};
36143805

0 commit comments

Comments
 (0)