Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Py_GetConstant() and Py_GetConstantBorrowed() #87

Merged
merged 1 commit into from
Mar 21, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions docs/api.rst
Original file line number Diff line number Diff line change
Expand Up @@ -192,6 +192,15 @@ Python 3.12

Not available on PyPy.

.. c:function:: PyObject* Py_GetConstant(unsigned int constant_id)

See `Py_GetConstant() documentation <https://docs.python.org/dev/c-api/object.html#c.Py_GetConstant>`__.

.. c:function:: PyObject* Py_GetConstantBorrowed(unsigned int constant_id)

See `Py_GetConstantBorrowed() documentation <https://docs.python.org/dev/c-api/object.html#c.Py_GetConstantBorrowed>`__.


Not supported:

* ``PyDict_AddWatcher()``, ``PyDict_Watch()``.
Expand Down
5 changes: 5 additions & 0 deletions docs/changelog.rst
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
Changelog
=========

* 2024-03-21: Add functions:

* ``Py_GetConstant()``
* ``Py_GetConstantBorrowed()``

* 2024-03-09: Add hash constants:

* ``PyHASH_BITS``
Expand Down
77 changes: 77 additions & 0 deletions pythoncapi_compat.h
Original file line number Diff line number Diff line change
Expand Up @@ -1209,6 +1209,83 @@ 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_CONSTANT_EMPTY_TUPLE + 1] = {NULL};

if (constants[Py_CONSTANT_NONE] == NULL) {
constants[Py_CONSTANT_NONE] = Py_None;
constants[Py_CONSTANT_FALSE] = Py_False;
constants[Py_CONSTANT_TRUE] = Py_True;
constants[Py_CONSTANT_ELLIPSIS] = Py_Ellipsis;
constants[Py_CONSTANT_NOT_IMPLEMENTED] = Py_NotImplemented;

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
Expand Down
86 changes: 86 additions & 0 deletions tests/test_pythoncapi_compat_cext.c
Original file line number Diff line number Diff line change
Expand Up @@ -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},
Expand Down Expand Up @@ -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}
};

Expand Down
Loading