diff --git a/docs/api.rst b/docs/api.rst index 162b27b..598c1cb 100644 --- a/docs/api.rst +++ b/docs/api.rst @@ -192,6 +192,15 @@ Python 3.12 Not available on PyPy. +.. c:function:: PyObject* Py_GetConstant(unsigned int constant_id) + + See `Py_GetConstant() documentation `__. + +.. c:function:: PyObject* Py_GetConstantBorrowed(unsigned int constant_id) + + See `Py_GetConstantBorrowed() documentation `__. + + Not supported: * ``PyDict_AddWatcher()``, ``PyDict_Watch()``. diff --git a/docs/changelog.rst b/docs/changelog.rst index 2569314..f869e10 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -1,6 +1,11 @@ Changelog ========= +* 2024-03-21: Add functions: + + * ``Py_GetConstant()`` + * ``Py_GetConstantBorrowed()`` + * 2024-03-09: Add hash constants: * ``PyHASH_BITS`` diff --git a/pythoncapi_compat.h b/pythoncapi_compat.h index 7164b46..424b404 100644 --- a/pythoncapi_compat.h +++ b/pythoncapi_compat.h @@ -1209,6 +1209,88 @@ static inline int PyTime_PerfCounter(PyTime_t *result) #endif +// gh-111545 added Py_GetConstant() and Py_GetConstantBorrowed() +// to Python 3.13.0a6 +#if PY_VERSION_HEX < 0x030D00A6 + +#define Py_CONSTANT_NONE 0 +#define Py_CONSTANT_FALSE 1 +#define Py_CONSTANT_TRUE 2 +#define Py_CONSTANT_ELLIPSIS 3 +#define Py_CONSTANT_NOT_IMPLEMENTED 4 +#define Py_CONSTANT_ZERO 5 +#define Py_CONSTANT_ONE 6 +#define Py_CONSTANT_EMPTY_STR 7 +#define Py_CONSTANT_EMPTY_BYTES 8 +#define Py_CONSTANT_EMPTY_TUPLE 9 + +static inline PyObject* Py_GetConstant(unsigned int constant_id) +{ + static PyObject* constants[] = { + Py_None, // Py_CONSTANT_NONE + Py_False, // Py_CONSTANT_FALSE + Py_True, // Py_CONSTANT_TRUE + Py_Ellipsis, // Py_CONSTANT_ELLIPSIS + Py_NotImplemented, // Py_CONSTANT_NOT_IMPLEMENTED + NULL, // Py_CONSTANT_ZERO + NULL, // Py_CONSTANT_ONE + NULL, // Py_CONSTANT_EMPTY_STR + NULL, // Py_CONSTANT_EMPTY_BYTES + NULL, // Py_CONSTANT_EMPTY_TUPLE + }; + + if (constants[Py_CONSTANT_ZERO] == NULL) { + constants[Py_CONSTANT_ZERO] = PyLong_FromLong(0); + if (constants[Py_CONSTANT_ZERO] == NULL) { + goto fatal_error; + } + + constants[Py_CONSTANT_ONE] = PyLong_FromLong(1); + if (constants[Py_CONSTANT_ONE] == NULL) { + goto fatal_error; + } + + constants[Py_CONSTANT_EMPTY_STR] = PyUnicode_FromStringAndSize("", 0); + if (constants[Py_CONSTANT_EMPTY_STR] == NULL) { + goto fatal_error; + } + + constants[Py_CONSTANT_EMPTY_BYTES] = PyBytes_FromStringAndSize("", 0); + if (constants[Py_CONSTANT_EMPTY_BYTES] == NULL) { + goto fatal_error; + } + + constants[Py_CONSTANT_EMPTY_TUPLE] = PyTuple_New(0); + if (constants[Py_CONSTANT_EMPTY_TUPLE] == NULL) { + goto fatal_error; + } + // goto dance to avoid compiler warnings about Py_FatalError() + goto init_done; + +fatal_error: + // This case should never happen + Py_FatalError("Py_GetConstant() failed to get constants"); + } + +init_done: + if (constant_id <= Py_CONSTANT_EMPTY_TUPLE) { + return Py_NewRef(constants[constant_id]); + } + else { + PyErr_BadInternalCall(); + return NULL; + } +} + +static inline PyObject* Py_GetConstantBorrowed(unsigned int constant_id) +{ + PyObject *obj = Py_GetConstant(constant_id); + Py_XDECREF(obj); + return obj; +} +#endif + + #ifdef __cplusplus } #endif diff --git a/tests/test_pythoncapi_compat_cext.c b/tests/test_pythoncapi_compat_cext.c index 4e9fb83..d906f94 100644 --- a/tests/test_pythoncapi_compat_cext.c +++ b/tests/test_pythoncapi_compat_cext.c @@ -1573,6 +1573,91 @@ test_time(PyObject *Py_UNUSED(module), PyObject *Py_UNUSED(args)) #endif +static void +check_get_constant(PyObject* (*get_constant)(unsigned int), int borrowed) +{ +#define CLEAR(var) if (!borrowed) { Py_DECREF(var); } + + PyObject *obj, *expected; + + // Py_CONSTANT_NONE + obj = get_constant(Py_CONSTANT_NONE); + assert(obj == Py_None); + CLEAR(obj); + + // Py_CONSTANT_FALSE + obj = get_constant(Py_CONSTANT_FALSE); + assert(obj = Py_False); + CLEAR(obj); + + // Py_CONSTANT_TRUE + obj = get_constant(Py_CONSTANT_TRUE); + assert(obj == Py_True); + CLEAR(obj); + + // Py_CONSTANT_ELLIPSIS + obj = get_constant(Py_CONSTANT_ELLIPSIS); + assert(obj == Py_Ellipsis); + CLEAR(obj); + + // Py_CONSTANT_NOT_IMPLEMENTED + obj = get_constant(Py_CONSTANT_NOT_IMPLEMENTED); + assert(obj == Py_NotImplemented); + CLEAR(obj); + + // Py_CONSTANT_ZERO + obj = get_constant(Py_CONSTANT_ZERO); + expected = PyLong_FromLong(0); + assert(expected != NULL); + assert(Py_TYPE(obj) == &PyLong_Type); + assert(PyObject_RichCompareBool(obj, expected, Py_EQ) == 1); + CLEAR(obj); + Py_DECREF(expected); + + // Py_CONSTANT_ONE + obj = get_constant(Py_CONSTANT_ONE); + expected = PyLong_FromLong(1); + assert(expected != NULL); + assert(Py_TYPE(obj) == &PyLong_Type); + assert(PyObject_RichCompareBool(obj, expected, Py_EQ) == 1); + CLEAR(obj); + Py_DECREF(expected); + + // Py_CONSTANT_EMPTY_STR + obj = get_constant(Py_CONSTANT_EMPTY_STR); + assert(Py_TYPE(obj) == &PyUnicode_Type); +#if PY_VERSION_HEX >= 0x03030000 + assert(PyUnicode_GetLength(obj) == 0); +#else + assert(PyUnicode_GetSize(obj) == 0); +#endif + CLEAR(obj); + + // Py_CONSTANT_EMPTY_BYTES + obj = get_constant(Py_CONSTANT_EMPTY_BYTES); + assert(Py_TYPE(obj) == &PyBytes_Type); + assert(PyBytes_Size(obj) == 0); + CLEAR(obj); + + // Py_CONSTANT_EMPTY_TUPLE + obj = get_constant(Py_CONSTANT_EMPTY_TUPLE); + assert(Py_TYPE(obj) == &PyTuple_Type); + assert(PyTuple_Size(obj) == 0); + CLEAR(obj); + +#undef CLEAR +} + + +static PyObject * +test_get_constant(PyObject *Py_UNUSED(module), PyObject *Py_UNUSED(args)) +{ + check_get_constant(Py_GetConstant, 0); + check_get_constant(Py_GetConstantBorrowed, 1); + Py_RETURN_NONE; +} + + static struct PyMethodDef methods[] = { {"test_object", test_object, METH_NOARGS, _Py_NULL}, {"test_py_is", test_py_is, METH_NOARGS, _Py_NULL}, @@ -1609,6 +1694,7 @@ static struct PyMethodDef methods[] = { #ifdef TEST_PYTIME {"test_time", test_time, METH_NOARGS, _Py_NULL}, #endif + {"test_get_constant", test_get_constant, METH_NOARGS, _Py_NULL}, {_Py_NULL, _Py_NULL, 0, _Py_NULL} };