diff --git a/docs/api.rst b/docs/api.rst index efd1387..88bb854 100644 --- a/docs/api.rst +++ b/docs/api.rst @@ -24,6 +24,22 @@ Latest version of the header file: `pythoncapi_compat.h `_. +Python 3.12 +----------- + +.. c:function:: PyObject* PyFrame_GetVar(PyFrameObject *frame, PyObject *name) + + See `PyFrame_GetVar() documentation `__. + + Not available on PyPy. + +.. c:function:: PyObject* PyFrame_GetVarString(PyFrameObject *frame, const char *name) + + See `PyFrame_GetVarString() documentation `__. + + Not available on PyPy. + + Python 3.11 ----------- diff --git a/docs/changelog.rst b/docs/changelog.rst index d733e0d..fda2bd4 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -1,6 +1,8 @@ Changelog ========= +* 2022-11-04: Add ``PyFrame_GetVar()`` and ``PyFrame_GetVarString()`` + functions. * 2022-08-04: Add ``PyCode_GetVarnames()``, ``PyCode_GetFreevars()`` and ``PyCode_GetCellvars()`` functions. * 2022-06-14: Fix compatibility with C++ older than C++11. diff --git a/pythoncapi_compat.h b/pythoncapi_compat.h index d422996..1a4e9ab 100644 --- a/pythoncapi_compat.h +++ b/pythoncapi_compat.h @@ -242,6 +242,57 @@ PyFrame_GetLasti(PyFrameObject *frame) #endif +// gh-91248 added PyFrame_GetVar() to Python 3.12.0a2 +#if PY_VERSION_HEX < 0x030C00A2 && !defined(PYPY_VERSION) +PYCAPI_COMPAT_STATIC_INLINE(PyObject*) +PyFrame_GetVar(PyFrameObject *frame, PyObject *name) +{ + PyObject *locals, *value; + + locals = PyFrame_GetLocals(frame); + if (locals == NULL) { + return NULL; + } +#if PY_VERSION_HEX >= 0x03000000 + value = PyDict_GetItemWithError(locals, name); +#else + value = PyDict_GetItem(locals, name); +#endif + Py_DECREF(locals); + + if (value == NULL) { + if (PyErr_Occurred()) { + return NULL; + } +#if PY_VERSION_HEX >= 0x03000000 + PyErr_Format(PyExc_NameError, "variable %R does not exist", name); +#else + PyErr_SetString(PyExc_NameError, "variable does not exist"); +#endif + return NULL; + } + return Py_NewRef(value); +} +#endif + + +// gh-91248 added PyFrame_GetVarString() to Python 3.12.0a2 +#if PY_VERSION_HEX < 0x030C00A2 && !defined(PYPY_VERSION) +PYCAPI_COMPAT_STATIC_INLINE(PyObject*) +PyFrame_GetVarString(PyFrameObject *frame, const char *name) +{ + PyObject *name_obj, *value; + name_obj = PyUnicode_FromString(name); + if (name_obj == NULL) { + return NULL; + } + value = PyFrame_GetVar(frame, name_obj); + Py_DECREF(name_obj); + return value; +} +#endif + + // bpo-39947 added PyThreadState_GetInterpreter() to Python 3.9.0a5 #if PY_VERSION_HEX < 0x030900A5 PYCAPI_COMPAT_STATIC_INLINE(PyInterpreterState *) diff --git a/tests/test_pythoncapi_compat_cext.c b/tests/test_pythoncapi_compat_cext.c index c43e355..e7af3d2 100644 --- a/tests/test_pythoncapi_compat_cext.c +++ b/tests/test_pythoncapi_compat_cext.c @@ -3,6 +3,10 @@ #include "pythoncapi_compat.h" +#ifdef NDEBUG +# error "assertions must be enabled" +#endif + #ifdef Py_LIMITED_API # error "Py_LIMITED_API is not supported" #endif @@ -138,6 +142,41 @@ test_py_is(PyObject *Py_UNUSED(module), PyObject* Py_UNUSED(ignored)) #if !defined(PYPY_VERSION) +static void +test_frame_getvar(PyFrameObject *frame) +{ + // Make the assumption that test_frame_getvar() is only called by + // _run_tests() of test_pythoncapi_compat.py and so that the "name" + // variable exists. + + // test PyFrame_GetVar() and PyFrame_GetVarString() + PyObject *attr = PyUnicode_FromString("name"); + assert(attr != NULL); + PyObject *name1 = PyFrame_GetVar(frame, attr); + Py_DECREF(attr); + assert(name1 != NULL); + Py_DECREF(name1); + + PyObject *name2 = PyFrame_GetVarString(frame, "name"); + assert(name2 != NULL); + Py_DECREF(name2); + + // test PyFrame_GetVar() and PyFrame_GetVarString() NameError + PyObject *attr3 = PyUnicode_FromString("dontexist"); + assert(attr3 != NULL); + PyObject *name3 = PyFrame_GetVar(frame, attr3); + Py_DECREF(attr3); + assert(name3 == NULL); + assert(PyErr_ExceptionMatches(PyExc_NameError)); + PyErr_Clear(); + + PyObject *name4 = PyFrame_GetVarString(frame, "dontexist"); + assert(name4 == NULL); + assert(PyErr_ExceptionMatches(PyExc_NameError)); + PyErr_Clear(); +} + + static PyObject * test_frame(PyObject *Py_UNUSED(module), PyObject* Py_UNUSED(ignored)) { @@ -214,6 +253,10 @@ test_frame(PyObject *Py_UNUSED(module), PyObject* Py_UNUSED(ignored)) int lasti = PyFrame_GetLasti(frame); assert(lasti >= 0); + // test PyFrame_GetVar() and PyFrame_GetVarString() + test_frame_getvar(frame); + + // done Py_DECREF(frame); Py_RETURN_NONE; }