Skip to content

Commit

Permalink
Add PyDict_GetItemRef()
Browse files Browse the repository at this point in the history
  • Loading branch information
vstinner committed Jul 21, 2023
1 parent c7dac97 commit eaff3c1
Show file tree
Hide file tree
Showing 5 changed files with 166 additions and 7 deletions.
16 changes: 12 additions & 4 deletions docs/api.rst
Expand Up @@ -27,13 +27,17 @@ Latest version of the header file:
Python 3.13
-----------

.. c:function:: PyObject* PyImport_AddModuleRef(const char *name)
.. c:function:: int PyDict_GetItemRef(PyObject *p, PyObject *key, PyObject **result)
See `PyImport_AddModuleRef() documentation <https://docs.python.org/dev/c-api/import.html#c.PyImport_AddModuleRef>`__.
See `PyDict_GetItemRef() documentation <https://docs.python.org/dev/c-api/dict.html#c.PyDict_GetItemRef>`__.
.. c:function:: int PyWeakref_GetRef(PyObject *ref, PyObject **pobj)
.. c:function:: int PyDict_GetItemStringRef(PyObject *p, const char *key, PyObject **result)
See `PyWeakref_GetRef() documentation <https://docs.python.org/dev/c-api/weakref.html#c.PyWeakref_GetRef>`__.
See `PyDict_GetItemStringRef() documentation <https://docs.python.org/dev/c-api/dict.html#c.PyDict_GetItemStringRef>`__.
.. c:function:: PyObject* PyImport_AddModuleRef(const char *name)
See `PyImport_AddModuleRef() documentation <https://docs.python.org/dev/c-api/import.html#c.PyImport_AddModuleRef>`__.
.. c:function:: int PyObject_GetOptionalAttr(PyObject *obj, PyObject *attr_name, PyObject **result)
Expand All @@ -55,6 +59,10 @@ Python 3.13
See `PyModule_Add() documentation <https://docs.python.org/dev/c-api/module.html#c.PyModule_Add>`__.
.. c:function:: int PyWeakref_GetRef(PyObject *ref, PyObject **pobj)
See `PyWeakref_GetRef() documentation <https://docs.python.org/dev/c-api/weakref.html#c.PyWeakref_GetRef>`__.
Python 3.12
-----------
Expand Down
1 change: 1 addition & 0 deletions docs/changelog.rst
@@ -1,6 +1,7 @@
Changelog
=========

* 2023-07-21: Add ``PyDict_GetItemRef()`` function.
* 2023-07-18: Add ``PyModule_Add()`` function.
* 2023-07-12: Add ``PyObject_GetOptionalAttr()``,
``PyObject_GetOptionalAttrString()``,
Expand Down
3 changes: 1 addition & 2 deletions docs/users.rst
Expand Up @@ -38,11 +38,10 @@ Examples of projects using pythoncapi_compat.h
(`commit <https://github.com/sergey-dryabzhinsky/python-zstd/commit/8aa6d7a4b250e1f0a4e27b4107c39dc516c87f96>`__)
* `hollerith <https://github.com/pyansys/hollerith/>`_
``src/writer.c`` uses ``PyObject_CallOneArg() and other Python 3.9 apis``
(`pythoncapi_compat.h copy
(`pythoncapi_compat.h copy
<https://github.com/pyansys/hollerith/blob/main/src/pythoncapi_compat.h>`__)



Projects not using pythoncapi_compat.h
======================================

Expand Down
45 changes: 44 additions & 1 deletion pythoncapi_compat.h
Expand Up @@ -258,7 +258,7 @@ PyFrame_GetVar(PyFrameObject *frame, PyObject *name)
#if PY_VERSION_HEX >= 0x03000000
value = PyDict_GetItemWithError(locals, name);
#else
value = PyDict_GetItem(locals, name);
value = _PyDict_GetItemWithError(locals, name);
#endif
Py_DECREF(locals);

Expand Down Expand Up @@ -791,6 +791,49 @@ PyMapping_GetOptionalItemString(PyObject *obj, const char *key, PyObject **resul
#endif


// gh-106004 added PyDict_GetItemRef() and PyDict_GetItemStringRef()
// to Python 3.13.0a1
#if PY_VERSION_HEX < 0x030D00A1
PYCAPI_COMPAT_STATIC_INLINE(int)
PyDict_GetItemRef(PyObject *mp, PyObject *key, PyObject **result)
{
#if PY_VERSION_HEX >= 0x03000000
PyObject *item = PyDict_GetItemWithError(mp, key);
#else
PyObject *item = _PyDict_GetItemWithError(mp, key);
#endif
if (item != NULL) {
*result = Py_NewRef(item);
return 1; // found
}
if (!PyErr_Occurred()) {
*result = NULL;
return 0; // not found
}
*result = NULL;
return -1;
}

PYCAPI_COMPAT_STATIC_INLINE(int)
PyDict_GetItemStringRef(PyObject *mp, const char *key, PyObject **result)
{
int res;
#if PY_VERSION_HEX >= 0x03000000
PyObject *key_obj = PyUnicode_FromString(key);
#else
PyObject *key_obj = PyString_FromString(key);
#endif
if (key_obj == NULL) {
*result = NULL;
return -1;
}
res = PyDict_GetItemRef(mp, key_obj, result);
Py_DECREF(key_obj);
return res;
}
#endif


// gh-106307 added PyModule_Add() to Python 3.13.0a1
#if PY_VERSION_HEX < 0x030D00A1
PYCAPI_COMPAT_STATIC_INLINE(int)
Expand Down
108 changes: 108 additions & 0 deletions tests/test_pythoncapi_compat_cext.c
Expand Up @@ -1086,6 +1086,113 @@ test_getitem(PyObject *Py_UNUSED(module), PyObject *Py_UNUSED(args))
}


static PyObject *
test_dict_getitemref(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;
PyObject *invalid_dict = NULL;
PyObject *get_value = 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;
}

res = PyDict_SetItemString(dict, "key", value);
if (res < 0) {
goto error;
}
assert(res == 0);

// test PyDict_GetItemRef(), key is present
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_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_GetItemRef(), invalid dict
invalid_dict = key; // borrowed reference
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); // not hashable key
if (invalid_key == NULL) {
goto error;
}

// 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);

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 struct PyMethodDef methods[] = {
{"test_object", test_object, METH_NOARGS, _Py_NULL},
{"test_py_is", test_py_is, METH_NOARGS, _Py_NULL},
Expand All @@ -1110,6 +1217,7 @@ static struct PyMethodDef methods[] = {
{"test_vectorcall", test_vectorcall, METH_NOARGS, _Py_NULL},
{"test_getattr", test_getattr, METH_NOARGS, _Py_NULL},
{"test_getitem", test_getitem, METH_NOARGS, _Py_NULL},
{"test_dict_getitemref", test_dict_getitemref, METH_NOARGS, _Py_NULL},
{_Py_NULL, _Py_NULL, 0, _Py_NULL}
};

Expand Down

0 comments on commit eaff3c1

Please sign in to comment.