From 0241c710d903beafdf50bfb172756a5f110a1cbc Mon Sep 17 00:00:00 2001 From: Michael Tremer Date: Fri, 14 Mar 2025 15:20:47 +0000 Subject: [PATCH] python: Allow passing a confirm callback from Python Signed-off-by: Michael Tremer --- src/python/ctx.c | 55 +++++++++++++++++++++++++++++++++++++++++++++ src/python/ctx.h | 5 +++++ tests/python/ctx.py | 25 +++++++++++++++++++++ 3 files changed, 85 insertions(+) diff --git a/src/python/ctx.c b/src/python/ctx.c index 122fd647..375f7308 100644 --- a/src/python/ctx.c +++ b/src/python/ctx.c @@ -197,6 +197,7 @@ static void Ctx_dealloc(CtxObject* self) { pakfire_ctx_unref(self->ctx); } + Py_XDECREF(self->callbacks.confirm); Py_XDECREF(self->logger); Py_TYPE(self)->tp_free((PyObject *)self); @@ -216,6 +217,54 @@ static PyObject* Ctx_set_logger(CtxObject* self, PyObject* args) { Py_RETURN_NONE; } +// Confirm Callback + +static int Ctx_confirm_callback(struct pakfire_ctx* ctx, struct pakfire* pakfire, + void* data, const char* message, const char* question) { + PyObject* callback = data; + PyObject* ret = NULL; + int r = 0; + + // Call the callback function + ret = PyObject_CallFunction(callback, "ss", message, question); + if (!ret) + return -1; + + // Return if the result is True + if (PyObject_IsTrue(ret)) + r = 1; + + // Cleanup + Py_DECREF(ret); + + return r; +} + +static PyObject* Ctx_set_confirm_callback(CtxObject* self, PyObject* args) { + PyObject* callback = NULL; + + // Parse arguments + if (!PyArg_ParseTuple(args, "O", &callback)) + return NULL; + + // Check if the callback is actually callable + if (!PyCallable_Check(callback)) { + PyErr_SetString(PyExc_ValueError, "Callback is not callable"); + return NULL; + } + + // Store a reference to the callback + self->callbacks.confirm = callback; + Py_INCREF(self->callbacks.confirm); + + // Set the confirm callback of the context + pakfire_ctx_set_confirm_callback(self->ctx, Ctx_confirm_callback, self->callbacks.confirm); + + Py_RETURN_NONE; +} + +// Cache Path + static PyObject* Ctx_get_cache_path(CtxObject* self) { const char* path = NULL; @@ -251,6 +300,12 @@ static int Ctx_set_cache_path(CtxObject* self, PyObject* value) { } static struct PyMethodDef Ctx_methods[] = { + { + "set_confirm_callback", + (PyCFunction)Ctx_set_confirm_callback, + METH_VARARGS, + NULL, + }, { "set_logger", (PyCFunction)Ctx_set_logger, diff --git a/src/python/ctx.h b/src/python/ctx.h index 7ff1b0dc..81d12ad9 100644 --- a/src/python/ctx.h +++ b/src/python/ctx.h @@ -30,6 +30,11 @@ typedef struct { struct pakfire_ctx* ctx; PyObject* logger; + + // Callbacks + struct { + PyObject* confirm; + } callbacks; } CtxObject; extern PyTypeObject CtxType; diff --git a/tests/python/ctx.py b/tests/python/ctx.py index ab2b77db..80ce2d9e 100755 --- a/tests/python/ctx.py +++ b/tests/python/ctx.py @@ -87,6 +87,31 @@ class CtxTests(tests.TestCase): # Check if we have received anything self.assertTrue(logger.buffer) + # Callbacks + + def test_set_callbacks(self): + """ + Tries to set some callbacks. + + I am not sure how we should call these to test them... + """ + # Create a new context + ctx = self.setup_ctx() + + # Try setting something that we cannot call + with self.assertRaises(ValueError): + ctx.set_confirm_callback(None) + + # Create a simple callback function + def confirm_callback(message, question): + print(message) + print(question) + + return True + + # Set the confirm callback + ctx.set_confirm_callback(confirm_callback) + if __name__ == "__main__": tests.main() -- 2.39.5