Skip to content

Commit 671fb69

Browse files
authored
Add PyObject_HasAttrWithError() (#74)
Add PyObject_HasAttrWithError() and PyObject_HasAttrStringWithError() functions. Fix PyObject_GetOptionalAttrString(): set result to NULL on error.
1 parent 309c56d commit 671fb69

File tree

4 files changed

+78
-19
lines changed

4 files changed

+78
-19
lines changed

docs/api.rst

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,10 +46,18 @@ Python 3.13
4646
4747
See `PyObject_GetOptionalAttr() documentation <https://docs.python.org/dev/c-api/object.html#c.PyObject_GetOptionalAttr>`__.
4848
49-
.. c:function:: int PyObject_GetOptionalAttrString(PyObject *obj, const char *name, PyObject **result)
49+
.. c:function:: int PyObject_GetOptionalAttrString(PyObject *obj, const char *attr_name, PyObject **result)
5050
5151
See `PyObject_GetOptionalAttrString() documentation <https://docs.python.org/dev/c-api/object.html#c.PyObject_GetOptionalAttrString>`__.
5252
53+
.. c:function:: int PyObject_HasAttrWithError(PyObject *obj, PyObject *attr_name)
54+
55+
See `PyObject_HasAttrWithError() documentation <https://docs.python.org/dev/c-api/object.html#c.PyObject_HasAttrWithError>`__.
56+
57+
.. c:function:: int PyObject_HasAttrStringWithError(PyObject *obj, const char *attr_name)
58+
59+
See `PyObject_HasAttrStringWithError() documentation <https://docs.python.org/dev/c-api/object.html#c.PyObject_HasAttrStringWithError>`__.
60+
5361
.. c:function:: int PyMapping_GetOptionalItem(PyObject *obj, PyObject *key, PyObject **result)
5462
5563
See `PyMapping_GetOptionalItem() documentation <https://docs.python.org/dev/c-api/mapping.html#c.PyMapping_GetOptionalItem>`__.

docs/changelog.rst

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,25 @@
11
Changelog
22
=========
33

4+
* 2023-09-29: Add functions:
5+
6+
* ``PyMapping_HasKeyWithError()``
7+
* ``PyMapping_HasKeyStringWithError()``
8+
* ``PyObject_HasAttrWithError()``
9+
* ``PyObject_HasAttrStringWithError()``
10+
411
* 2023-08-25: Add ``PyDict_ContainsString()`` and ``PyLong_AsInt()`` functions.
512
* 2023-08-21: Remove support for Python 2.7, Python 3.4 and older.
613
* 2023-08-16: Add ``Py_IsFinalizing()`` function.
714
* 2023-07-21: Add ``PyDict_GetItemRef()`` function.
815
* 2023-07-18: Add ``PyModule_Add()`` function.
9-
* 2023-07-12: Add ``PyObject_GetOptionalAttr()``,
10-
``PyObject_GetOptionalAttrString()``,
11-
``PyMapping_GetOptionalItem()``
12-
and ``PyMapping_GetOptionalItemString()`` functions.
16+
* 2023-07-12: Add functions:
17+
18+
* ``PyObject_GetOptionalAttr()``
19+
* ``PyObject_GetOptionalAttrString()``
20+
* ``PyMapping_GetOptionalItem()``
21+
* ``PyMapping_GetOptionalItemString()``
22+
1323
* 2023-07-05: Add ``PyObject_Vectorcall()`` function.
1424
* 2023-06-21: Add ``PyWeakref_GetRef()`` function.
1525
* 2023-06-20: Add ``PyImport_AddModuleRef()`` function.

pythoncapi_compat.h

Lines changed: 32 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -667,16 +667,17 @@ PyObject_Vectorcall(PyObject *callable, PyObject *const *args,
667667
#endif
668668

669669

670-
// gh-106521 added PyObject_GetOptionalAttr() to Python 3.13.0a1
670+
// gh-106521 added PyObject_GetOptionalAttr() and
671+
// PyObject_GetOptionalAttrString() to Python 3.13.0a1
671672
#if PY_VERSION_HEX < 0x030D00A1
672673
static inline int
673-
PyObject_GetOptionalAttr(PyObject *obj, PyObject *name, PyObject **result)
674+
PyObject_GetOptionalAttr(PyObject *obj, PyObject *attr_name, PyObject **result)
674675
{
675676
// bpo-32571 added _PyObject_LookupAttr() to Python 3.7.0b1
676677
#if PY_VERSION_HEX >= 0x030700B1 && !defined(PYPY_VERSION)
677-
return _PyObject_LookupAttr(obj, name, result);
678+
return _PyObject_LookupAttr(obj, attr_name, result);
678679
#else
679-
*result = PyObject_GetAttr(obj, name);
680+
*result = PyObject_GetAttr(obj, attr_name);
680681
if (*result != NULL) {
681682
return 1;
682683
}
@@ -692,16 +693,17 @@ PyObject_GetOptionalAttr(PyObject *obj, PyObject *name, PyObject **result)
692693
}
693694

694695
static inline int
695-
PyObject_GetOptionalAttrString(PyObject *obj, const char *name, PyObject **result)
696+
PyObject_GetOptionalAttrString(PyObject *obj, const char *attr_name, PyObject **result)
696697
{
697698
PyObject *name_obj;
698699
int rc;
699700
#if PY_VERSION_HEX >= 0x03000000
700-
name_obj = PyUnicode_FromString(name);
701+
name_obj = PyUnicode_FromString(attr_name);
701702
#else
702-
name_obj = PyString_FromString(name);
703+
name_obj = PyString_FromString(attr_name);
703704
#endif
704705
if (name_obj == NULL) {
706+
*result = NULL;
705707
return -1;
706708
}
707709
rc = PyObject_GetOptionalAttr(obj, name_obj, result);
@@ -771,6 +773,29 @@ PyMapping_HasKeyStringWithError(PyObject *obj, const char *key)
771773
#endif
772774

773775

776+
// gh-108511 added PyObject_HasAttrWithError() and
777+
// PyObject_HasAttrStringWithError() to Python 3.13.0a1
778+
#if PY_VERSION_HEX < 0x030D00A1
779+
static inline int
780+
PyObject_HasAttrWithError(PyObject *obj, PyObject *attr)
781+
{
782+
PyObject *res;
783+
int rc = PyObject_GetOptionalAttr(obj, attr, &res);
784+
Py_XDECREF(res);
785+
return rc;
786+
}
787+
788+
static inline int
789+
PyObject_HasAttrStringWithError(PyObject *obj, const char *attr)
790+
{
791+
PyObject *res;
792+
int rc = PyObject_GetOptionalAttrString(obj, attr, &res);
793+
Py_XDECREF(res);
794+
return rc;
795+
}
796+
#endif
797+
798+
774799
// gh-106004 added PyDict_GetItemRef() and PyDict_GetItemStringRef()
775800
// to Python 3.13.0a1
776801
#if PY_VERSION_HEX < 0x030D00A1

tests/test_pythoncapi_compat_cext.c

Lines changed: 23 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1015,37 +1015,53 @@ test_getattr(PyObject *Py_UNUSED(module), PyObject *Py_UNUSED(args))
10151015
if (obj == _Py_NULL) {
10161016
return _Py_NULL;
10171017
}
1018-
PyObject *attr_name;
1019-
PyObject *value;
1018+
1019+
PyObject *attr_name = create_string("version");
1020+
PyObject *missing_attr = create_string("nonexistant_attr_name");
10201021

10211022
// test PyObject_GetOptionalAttr(): attribute exists
1022-
attr_name = create_string("version");
1023+
PyObject *value;
10231024
value = UNINITIALIZED_OBJ;
10241025
assert(PyObject_GetOptionalAttr(obj, attr_name, &value) == 1);
10251026
assert(value != _Py_NULL);
10261027
Py_DECREF(value);
1027-
Py_DECREF(attr_name);
1028+
1029+
// test PyObject_HasAttrWithError(): attribute exists
1030+
assert(PyObject_HasAttrWithError(obj, attr_name) == 1);
10281031

10291032
// test PyObject_GetOptionalAttrString(): attribute exists
10301033
value = UNINITIALIZED_OBJ;
10311034
assert(PyObject_GetOptionalAttrString(obj, "version", &value) == 1);
1035+
assert(!PyErr_Occurred());
10321036
assert(value != _Py_NULL);
10331037
Py_DECREF(value);
10341038

1039+
// test PyObject_HasAttrStringWithError(): attribute exists
1040+
assert(PyObject_HasAttrStringWithError(obj, "version") == 1);
1041+
assert(!PyErr_Occurred());
1042+
10351043
// test PyObject_GetOptionalAttr(): attribute doesn't exist
1036-
attr_name = create_string("nonexistant_attr_name");
10371044
value = UNINITIALIZED_OBJ;
1038-
assert(PyObject_GetOptionalAttr(obj, attr_name, &value) == 0);
1045+
assert(PyObject_GetOptionalAttr(obj, missing_attr, &value) == 0);
1046+
assert(!PyErr_Occurred());
10391047
assert(value == _Py_NULL);
1040-
Py_DECREF(attr_name);
1048+
1049+
// test PyObject_HasAttrWithError(): attribute doesn't exist
1050+
assert(PyObject_HasAttrWithError(obj, missing_attr) == 0);
10411051
assert(!PyErr_Occurred());
10421052

10431053
// test PyObject_GetOptionalAttrString(): attribute doesn't exist
10441054
value = UNINITIALIZED_OBJ;
10451055
assert(PyObject_GetOptionalAttrString(obj, "nonexistant_attr_name", &value) == 0);
1056+
assert(!PyErr_Occurred());
10461057
assert(value == _Py_NULL);
1058+
1059+
// test PyObject_HasAttrStringWithError(): attribute doesn't exist
1060+
assert(PyObject_HasAttrStringWithError(obj, "nonexistant_attr_name") == 0);
10471061
assert(!PyErr_Occurred());
10481062

1063+
Py_DECREF(attr_name);
1064+
Py_DECREF(missing_attr);
10491065
Py_DECREF(obj);
10501066
Py_RETURN_NONE;
10511067
}

0 commit comments

Comments
 (0)