diff --git a/docs/api.rst b/docs/api.rst index a797405..c0adfc4 100644 --- a/docs/api.rst +++ b/docs/api.rst @@ -46,10 +46,18 @@ Python 3.13 See `PyObject_GetOptionalAttr() documentation `__. -.. c:function:: int PyObject_GetOptionalAttrString(PyObject *obj, const char *name, PyObject **result) +.. c:function:: int PyObject_GetOptionalAttrString(PyObject *obj, const char *attr_name, PyObject **result) See `PyObject_GetOptionalAttrString() documentation `__. +.. c:function:: int PyObject_HasAttrWithError(PyObject *obj, PyObject *attr_name) + + See `PyObject_HasAttrWithError() documentation `__. + +.. c:function:: int PyObject_HasAttrStringWithError(PyObject *obj, const char *attr_name) + + See `PyObject_HasAttrStringWithError() documentation `__. + .. c:function:: int PyMapping_GetOptionalItem(PyObject *obj, PyObject *key, PyObject **result) See `PyMapping_GetOptionalItem() documentation `__. diff --git a/docs/changelog.rst b/docs/changelog.rst index bab8e65..7a21348 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -1,15 +1,25 @@ Changelog ========= +* 2023-09-29: Add functions: + + * ``PyMapping_HasKeyWithError()`` + * ``PyMapping_HasKeyStringWithError()`` + * ``PyObject_HasAttrWithError()`` + * ``PyObject_HasAttrStringWithError()`` + * 2023-08-25: Add ``PyDict_ContainsString()`` and ``PyLong_AsInt()`` functions. * 2023-08-21: Remove support for Python 2.7, Python 3.4 and older. * 2023-08-16: Add ``Py_IsFinalizing()`` function. * 2023-07-21: Add ``PyDict_GetItemRef()`` function. * 2023-07-18: Add ``PyModule_Add()`` function. -* 2023-07-12: Add ``PyObject_GetOptionalAttr()``, - ``PyObject_GetOptionalAttrString()``, - ``PyMapping_GetOptionalItem()`` - and ``PyMapping_GetOptionalItemString()`` functions. +* 2023-07-12: Add functions: + + * ``PyObject_GetOptionalAttr()`` + * ``PyObject_GetOptionalAttrString()`` + * ``PyMapping_GetOptionalItem()`` + * ``PyMapping_GetOptionalItemString()`` + * 2023-07-05: Add ``PyObject_Vectorcall()`` function. * 2023-06-21: Add ``PyWeakref_GetRef()`` function. * 2023-06-20: Add ``PyImport_AddModuleRef()`` function. diff --git a/pythoncapi_compat.h b/pythoncapi_compat.h index 9451f90..e6a388e 100644 --- a/pythoncapi_compat.h +++ b/pythoncapi_compat.h @@ -667,16 +667,17 @@ PyObject_Vectorcall(PyObject *callable, PyObject *const *args, #endif -// gh-106521 added PyObject_GetOptionalAttr() to Python 3.13.0a1 +// gh-106521 added PyObject_GetOptionalAttr() and +// PyObject_GetOptionalAttrString() to Python 3.13.0a1 #if PY_VERSION_HEX < 0x030D00A1 static inline int -PyObject_GetOptionalAttr(PyObject *obj, PyObject *name, PyObject **result) +PyObject_GetOptionalAttr(PyObject *obj, PyObject *attr_name, PyObject **result) { // bpo-32571 added _PyObject_LookupAttr() to Python 3.7.0b1 #if PY_VERSION_HEX >= 0x030700B1 && !defined(PYPY_VERSION) - return _PyObject_LookupAttr(obj, name, result); + return _PyObject_LookupAttr(obj, attr_name, result); #else - *result = PyObject_GetAttr(obj, name); + *result = PyObject_GetAttr(obj, attr_name); if (*result != NULL) { return 1; } @@ -692,16 +693,17 @@ PyObject_GetOptionalAttr(PyObject *obj, PyObject *name, PyObject **result) } static inline int -PyObject_GetOptionalAttrString(PyObject *obj, const char *name, PyObject **result) +PyObject_GetOptionalAttrString(PyObject *obj, const char *attr_name, PyObject **result) { PyObject *name_obj; int rc; #if PY_VERSION_HEX >= 0x03000000 - name_obj = PyUnicode_FromString(name); + name_obj = PyUnicode_FromString(attr_name); #else - name_obj = PyString_FromString(name); + name_obj = PyString_FromString(attr_name); #endif if (name_obj == NULL) { + *result = NULL; return -1; } rc = PyObject_GetOptionalAttr(obj, name_obj, result); @@ -771,6 +773,29 @@ PyMapping_HasKeyStringWithError(PyObject *obj, const char *key) #endif +// gh-108511 added PyObject_HasAttrWithError() and +// PyObject_HasAttrStringWithError() to Python 3.13.0a1 +#if PY_VERSION_HEX < 0x030D00A1 +static inline int +PyObject_HasAttrWithError(PyObject *obj, PyObject *attr) +{ + PyObject *res; + int rc = PyObject_GetOptionalAttr(obj, attr, &res); + Py_XDECREF(res); + return rc; +} + +static inline int +PyObject_HasAttrStringWithError(PyObject *obj, const char *attr) +{ + PyObject *res; + int rc = PyObject_GetOptionalAttrString(obj, attr, &res); + Py_XDECREF(res); + return rc; +} +#endif + + // gh-106004 added PyDict_GetItemRef() and PyDict_GetItemStringRef() // to Python 3.13.0a1 #if PY_VERSION_HEX < 0x030D00A1 diff --git a/tests/test_pythoncapi_compat_cext.c b/tests/test_pythoncapi_compat_cext.c index b3f22b3..7e08402 100644 --- a/tests/test_pythoncapi_compat_cext.c +++ b/tests/test_pythoncapi_compat_cext.c @@ -1015,37 +1015,53 @@ test_getattr(PyObject *Py_UNUSED(module), PyObject *Py_UNUSED(args)) if (obj == _Py_NULL) { return _Py_NULL; } - PyObject *attr_name; - PyObject *value; + + PyObject *attr_name = create_string("version"); + PyObject *missing_attr = create_string("nonexistant_attr_name"); // test PyObject_GetOptionalAttr(): attribute exists - attr_name = create_string("version"); + PyObject *value; value = UNINITIALIZED_OBJ; assert(PyObject_GetOptionalAttr(obj, attr_name, &value) == 1); assert(value != _Py_NULL); Py_DECREF(value); - Py_DECREF(attr_name); + + // test PyObject_HasAttrWithError(): attribute exists + assert(PyObject_HasAttrWithError(obj, attr_name) == 1); // test PyObject_GetOptionalAttrString(): attribute exists value = UNINITIALIZED_OBJ; assert(PyObject_GetOptionalAttrString(obj, "version", &value) == 1); + assert(!PyErr_Occurred()); assert(value != _Py_NULL); Py_DECREF(value); + // test PyObject_HasAttrStringWithError(): attribute exists + assert(PyObject_HasAttrStringWithError(obj, "version") == 1); + assert(!PyErr_Occurred()); + // test PyObject_GetOptionalAttr(): attribute doesn't exist - attr_name = create_string("nonexistant_attr_name"); value = UNINITIALIZED_OBJ; - assert(PyObject_GetOptionalAttr(obj, attr_name, &value) == 0); + assert(PyObject_GetOptionalAttr(obj, missing_attr, &value) == 0); + assert(!PyErr_Occurred()); assert(value == _Py_NULL); - Py_DECREF(attr_name); + + // test PyObject_HasAttrWithError(): attribute doesn't exist + assert(PyObject_HasAttrWithError(obj, missing_attr) == 0); assert(!PyErr_Occurred()); // test PyObject_GetOptionalAttrString(): attribute doesn't exist value = UNINITIALIZED_OBJ; assert(PyObject_GetOptionalAttrString(obj, "nonexistant_attr_name", &value) == 0); + assert(!PyErr_Occurred()); assert(value == _Py_NULL); + + // test PyObject_HasAttrStringWithError(): attribute doesn't exist + assert(PyObject_HasAttrStringWithError(obj, "nonexistant_attr_name") == 0); assert(!PyErr_Occurred()); + Py_DECREF(attr_name); + Py_DECREF(missing_attr); Py_DECREF(obj); Py_RETURN_NONE; }