Skip to content

Commit

Permalink
Add PyObject_HasAttrWithError() (#74)
Browse files Browse the repository at this point in the history
Add PyObject_HasAttrWithError() and PyObject_HasAttrStringWithError()
functions.

Fix PyObject_GetOptionalAttrString(): set result to NULL on error.
  • Loading branch information
vstinner authored Sep 29, 2023
1 parent 309c56d commit 671fb69
Show file tree
Hide file tree
Showing 4 changed files with 78 additions and 19 deletions.
10 changes: 9 additions & 1 deletion docs/api.rst
Original file line number Diff line number Diff line change
Expand Up @@ -46,10 +46,18 @@ Python 3.13
See `PyObject_GetOptionalAttr() documentation <https://docs.python.org/dev/c-api/object.html#c.PyObject_GetOptionalAttr>`__.
.. 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 <https://docs.python.org/dev/c-api/object.html#c.PyObject_GetOptionalAttrString>`__.
.. c:function:: int PyObject_HasAttrWithError(PyObject *obj, PyObject *attr_name)
See `PyObject_HasAttrWithError() documentation <https://docs.python.org/dev/c-api/object.html#c.PyObject_HasAttrWithError>`__.
.. c:function:: int PyObject_HasAttrStringWithError(PyObject *obj, const char *attr_name)
See `PyObject_HasAttrStringWithError() documentation <https://docs.python.org/dev/c-api/object.html#c.PyObject_HasAttrStringWithError>`__.
.. c:function:: int PyMapping_GetOptionalItem(PyObject *obj, PyObject *key, PyObject **result)
See `PyMapping_GetOptionalItem() documentation <https://docs.python.org/dev/c-api/mapping.html#c.PyMapping_GetOptionalItem>`__.
Expand Down
18 changes: 14 additions & 4 deletions docs/changelog.rst
Original file line number Diff line number Diff line change
@@ -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.
Expand Down
39 changes: 32 additions & 7 deletions pythoncapi_compat.h
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
Expand All @@ -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);
Expand Down Expand Up @@ -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
Expand Down
30 changes: 23 additions & 7 deletions tests/test_pythoncapi_compat_cext.c
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
Expand Down

0 comments on commit 671fb69

Please sign in to comment.