diff --git a/docs/api.rst b/docs/api.rst index 23315f8..9a8decc 100644 --- a/docs/api.rst +++ b/docs/api.rst @@ -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 `__. + See `PyDict_GetItemRef() documentation `__. -.. 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 `__. + See `PyDict_GetItemStringRef() documentation `__. + +.. c:function:: PyObject* PyImport_AddModuleRef(const char *name) + + See `PyImport_AddModuleRef() documentation `__. .. c:function:: int PyObject_GetOptionalAttr(PyObject *obj, PyObject *attr_name, PyObject **result) @@ -55,6 +59,10 @@ Python 3.13 See `PyModule_Add() documentation `__. +.. c:function:: int PyWeakref_GetRef(PyObject *ref, PyObject **pobj) + + See `PyWeakref_GetRef() documentation `__. + Python 3.12 ----------- diff --git a/docs/changelog.rst b/docs/changelog.rst index c9b98ad..d017844 100644 --- a/docs/changelog.rst +++ b/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()``, diff --git a/docs/users.rst b/docs/users.rst index 304dfda..d7d6a84 100644 --- a/docs/users.rst +++ b/docs/users.rst @@ -38,11 +38,10 @@ Examples of projects using pythoncapi_compat.h (`commit `__) * `hollerith `_ ``src/writer.c`` uses ``PyObject_CallOneArg() and other Python 3.9 apis`` - (`pythoncapi_compat.h copy + (`pythoncapi_compat.h copy `__) - Projects not using pythoncapi_compat.h ====================================== diff --git a/pythoncapi_compat.h b/pythoncapi_compat.h index 7fd6080..2a87a55 100644 --- a/pythoncapi_compat.h +++ b/pythoncapi_compat.h @@ -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); @@ -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) diff --git a/tests/test_pythoncapi_compat_cext.c b/tests/test_pythoncapi_compat_cext.c index 450d1ab..5210495 100644 --- a/tests/test_pythoncapi_compat_cext.c +++ b/tests/test_pythoncapi_compat_cext.c @@ -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}, @@ -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} };