Skip to content

Commit 157d6d4

Browse files
committed
Add PyObject_GetOptionalAttr() function
1 parent 1911dd4 commit 157d6d4

File tree

4 files changed

+115
-0
lines changed

4 files changed

+115
-0
lines changed

docs/api.rst

+9
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,15 @@ Python 3.13
3535
3636
See `PyWeakref_GetRef() documentation <https://docs.python.org/dev/c-api/weakref.html#c.PyWeakref_GetRef>`__.
3737
38+
.. c:function:: int PyObject_GetOptionalAttr(PyObject *obj, PyObject *attr_name, PyObject **result)
39+
40+
See `PyObject_GetOptionalAttr() documentation <https://docs.python.org/dev/c-api/object.html#c.PyObject_GetOptionalAttr>`__.
41+
42+
.. c:function:: int PyObject_GetOptionalAttrString(PyObject *obj, const char *name, PyObject **result)
43+
44+
See `PyObject_GetOptionalAttrString() documentation <https://docs.python.org/dev/c-api/object.html#c.PyObject_GetOptionalAttrString>`__.
45+
46+
3847
Python 3.12
3948
-----------
4049

docs/changelog.rst

+2
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
Changelog
22
=========
33

4+
* 2023-07-12: Add ``PyObject_GetOptionalAttr()`` and
5+
``PyObject_GetOptionalAttrString()`` functions.
46
* 2023-07-05: Add ``PyObject_Vectorcall()`` function.
57
* 2023-06-21: Add ``PyWeakref_GetRef()`` function.
68
* 2023-06-20: Add ``PyImport_AddModuleRef()`` function.

pythoncapi_compat.h

+43
Original file line numberDiff line numberDiff line change
@@ -698,6 +698,49 @@ PyObject_Vectorcall(PyObject *callable, PyObject *const *args,
698698
#endif
699699

700700

701+
// gh-106521 added PyObject_GetOptionalAttr() to Python 3.13.0a1
702+
#if PY_VERSION_HEX < 0x030D00A1
703+
PYCAPI_COMPAT_STATIC_INLINE(int)
704+
PyObject_GetOptionalAttr(PyObject *obj, PyObject *name, PyObject **result)
705+
{
706+
// bpo-32571 added _PyObject_LookupAttr() to Python 3.7.0b1
707+
#if PY_VERSION_HEX >= 0x030700B1 && !defined(PYPY_VERSION)
708+
return _PyObject_LookupAttr(obj, name, result);
709+
#else
710+
*result = PyObject_GetAttr(obj, name);
711+
if (*result != NULL) {
712+
return 1;
713+
}
714+
if (!PyErr_Occurred()) {
715+
return 0;
716+
}
717+
if (PyErr_ExceptionMatches(PyExc_AttributeError)) {
718+
PyErr_Clear();
719+
return 0;
720+
}
721+
return -1;
722+
#endif
723+
}
724+
725+
PYCAPI_COMPAT_STATIC_INLINE(int)
726+
PyObject_GetOptionalAttrString(PyObject *obj, const char *name, PyObject **presult)
727+
{
728+
PyObject *name_obj;
729+
#if PY_VERSION_HEX >= 0x03000000
730+
name_obj = PyUnicode_FromString(name);
731+
#else
732+
name_obj = PyString_FromString(name);
733+
#endif
734+
if (name_obj == NULL) {
735+
return -1;
736+
}
737+
int res = PyObject_GetOptionalAttr(obj, name_obj, presult);
738+
Py_DECREF(name_obj);
739+
return res;
740+
}
741+
#endif
742+
743+
701744
#ifdef __cplusplus
702745
}
703746
#endif

tests/test_pythoncapi_compat_cext.c

+61
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,21 @@
5050
# define ASSERT_REFCNT(expr)
5151
#endif
5252

53+
54+
static PyObject*
55+
create_string(const char *str)
56+
{
57+
PyObject *obj;
58+
#ifdef PYTHON3
59+
obj = PyUnicode_FromString(str);
60+
#else
61+
obj = PyString_FromString(str);
62+
#endif
63+
assert(obj != NULL);
64+
return obj;
65+
}
66+
67+
5368
static PyObject *
5469
test_object(PyObject *Py_UNUSED(module), PyObject* Py_UNUSED(ignored))
5570
{
@@ -930,6 +945,51 @@ test_vectorcall(PyObject *module, PyObject *Py_UNUSED(args))
930945
}
931946

932947

948+
static PyObject *
949+
test_getattr(PyObject *Py_UNUSED(module), PyObject *Py_UNUSED(args))
950+
{
951+
assert(!PyErr_Occurred());
952+
953+
PyObject *obj = PyImport_ImportModule("sys");
954+
if (obj == NULL) {
955+
return NULL;
956+
}
957+
PyObject *attr_name;
958+
PyObject *value;
959+
960+
// test PyObject_GetOptionalAttr(): attribute exists
961+
attr_name = create_string("version");
962+
value = Py_True; // marker value
963+
assert(PyObject_GetOptionalAttr(obj, attr_name, &value) == 1);
964+
assert(value != NULL);
965+
Py_DECREF(value);
966+
Py_DECREF(attr_name);
967+
968+
// test PyObject_GetOptionalAttrString(): attribute exists
969+
value = Py_True; // marker value
970+
assert(PyObject_GetOptionalAttrString(obj, "version", &value) == 1);
971+
assert(value != NULL);
972+
Py_DECREF(value);
973+
974+
// test PyObject_GetOptionalAttr(): attribute doesn't exist
975+
attr_name = create_string("nonexistant_attr_name");
976+
value = Py_True; // marker value
977+
assert(PyObject_GetOptionalAttr(obj, attr_name, &value) == 0);
978+
assert(value == NULL);
979+
Py_DECREF(attr_name);
980+
assert(!PyErr_Occurred());
981+
982+
// test PyObject_GetOptionalAttrString(): attribute doesn't exist
983+
value = Py_True; // marker value
984+
assert(PyObject_GetOptionalAttrString(obj, "nonexistant_attr_name", &value) == 0);
985+
assert(value == NULL);
986+
assert(!PyErr_Occurred());
987+
988+
Py_DECREF(obj);
989+
Py_RETURN_NONE;
990+
}
991+
992+
933993
static struct PyMethodDef methods[] = {
934994
{"test_object", test_object, METH_NOARGS, _Py_NULL},
935995
{"test_py_is", test_py_is, METH_NOARGS, _Py_NULL},
@@ -952,6 +1012,7 @@ static struct PyMethodDef methods[] = {
9521012
{"test_weakref", test_weakref, METH_NOARGS, _Py_NULL},
9531013
{"func_varargs", (PyCFunction)(void*)func_varargs, METH_VARARGS | METH_KEYWORDS, _Py_NULL},
9541014
{"test_vectorcall", test_vectorcall, METH_NOARGS, _Py_NULL},
1015+
{"test_getattr", test_getattr, METH_NOARGS, _Py_NULL},
9551016
{_Py_NULL, _Py_NULL, 0, _Py_NULL}
9561017
};
9571018

0 commit comments

Comments
 (0)