]> git.ipfire.org Git - pakfire.git/commitdiff
python: Add a basic Transaction class
authorMichael Tremer <michael.tremer@ipfire.org>
Fri, 14 Mar 2025 11:04:48 +0000 (11:04 +0000)
committerMichael Tremer <michael.tremer@ipfire.org>
Fri, 14 Mar 2025 11:05:26 +0000 (11:05 +0000)
Signed-off-by: Michael Tremer <michael.tremer@ipfire.org>
Makefile.am
src/python/pakfiremodule.c
src/python/transaction.c [new file with mode: 0644]
src/python/transaction.h [new file with mode: 0644]
tests/python/transaction.py [new file with mode: 0755]

index 1a82140a56065a94a7885902d1fd8f9cd8c557ac..48319e9b86ce1b13f9f5d2064244cbe5e84b9da9 100644 (file)
@@ -125,6 +125,8 @@ pakfire_la_SOURCES = \
        src/python/repo.h \
        src/python/solution.c \
        src/python/solution.h \
+       src/python/transaction.c \
+       src/python/transaction.h \
        src/python/util.c \
        src/python/util.h
 
@@ -1269,6 +1271,7 @@ dist_check_SCRIPTS = \
        tests/python/keys.py \
        tests/python/main.py \
        tests/python/package.py \
+       tests/python/transaction.py \
        tests/python/version_compare.py
 
 EXTRA_DIST += \
index 4c2c3a7b9d538b2ec7b53589f48265218c2f5956..af7b9acb10f91c6fcc9639f95dc5af7922f08d19 100644 (file)
@@ -35,6 +35,7 @@
 #include "problem.h"
 #include "repo.h"
 #include "solution.h"
+#include "transaction.h"
 #include "util.h"
 
 PyMODINIT_FUNC PyInit_pakfire(void);
@@ -183,6 +184,13 @@ PyMODINIT_FUNC PyInit_pakfire(void) {
        Py_INCREF(&SolutionType);
        PyModule_AddObject(module, "Solution", (PyObject *)&SolutionType);
 
+       // Transaction
+       if (PyType_Ready(&TransactionType) < 0)
+               return NULL;
+
+       Py_INCREF(&TransactionType);
+       PyModule_AddObject(module, "Transaction", (PyObject *)&TransactionType);
+
        // Constants
        if (PyModule_AddIntMacro(module, PAKFIRE_KEY_ALGO_NULL) < 0)
                goto ERROR;
diff --git a/src/python/transaction.c b/src/python/transaction.c
new file mode 100644 (file)
index 0000000..8dfb7af
--- /dev/null
@@ -0,0 +1,202 @@
+/*#############################################################################
+#                                                                             #
+# Pakfire - The IPFire package management system                              #
+# Copyright (C) 2025 Pakfire development team                                 #
+#                                                                             #
+# This program is free software: you can redistribute it and/or modify        #
+# it under the terms of the GNU General Public License as published by        #
+# the Free Software Foundation, either version 3 of the License, or           #
+# (at your option) any later version.                                         #
+#                                                                             #
+# This program is distributed in the hope that it will be useful,             #
+# but WITHOUT ANY WARRANTY; without even the implied warranty of              #
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the               #
+# GNU General Public License for more details.                                #
+#                                                                             #
+# You should have received a copy of the GNU General Public License           #
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.       #
+#                                                                             #
+#############################################################################*/
+
+#define PY_SSIZE_T_CLEAN
+#include <Python.h>
+
+#include <pakfire/transaction.h>
+
+#include "errors.h"
+#include "pakfire.h"
+#include "transaction.h"
+
+static PyObject* Transaction_new(PyTypeObject* type, PyObject* args, PyObject* kwds) {
+       TransactionObject* self = (TransactionObject *)type->tp_alloc(type, 0);
+       if (self) {
+               self->transaction = NULL;
+       }
+
+       return (PyObject*)self;
+}
+
+static void Transaction_dealloc(TransactionObject* self) {
+       if (self->transaction)
+               pakfire_transaction_unref(self->transaction);
+
+       Py_TYPE(self)->tp_free((PyObject *)self);
+}
+
+static int Transaction_init(TransactionObject* self, PyObject* args, PyObject* kwargs) {
+       PakfireObject* pakfire = NULL;
+       int allow_downgrade = 0;
+       int allow_uninstall = 0;
+       int without_recommended = 0;
+       int flags = 0;
+       int r;
+
+       const char* kwlist[] = {
+               "pakfire",
+               "allow_downgrade",
+               "allow_uninstall",
+               "without_recommended",
+               NULL,
+       };
+
+       if (!PyArg_ParseTupleAndKeywords(args, kwargs, "O!|bbb", (char**)kwlist,
+                       &PakfireType, &pakfire, &allow_downgrade, &allow_uninstall, &without_recommended))
+               return -1;
+
+       if (allow_downgrade)
+               flags |= PAKFIRE_TRANSACTION_ALLOW_DOWNGRADE;
+
+       if (allow_uninstall)
+               flags |= PAKFIRE_TRANSACTION_ALLOW_UNINSTALL;
+
+       if (without_recommended)
+               flags |= PAKFIRE_TRANSACTION_WITHOUT_RECOMMENDED;
+
+       // Create a new transaction
+       r = pakfire_transaction_create(&self->transaction, pakfire->pakfire, flags);
+       if (r < 0) {
+               errno = -r;
+               PyErr_SetFromErrno(PyExc_OSError);
+               return -1;
+       }
+
+       return 0;
+}
+
+static PyObject* Transaction_request(TransactionObject* self,
+               const enum pakfire_job_action action, PyObject* args, PyObject* kwargs) {
+       const char* what = NULL;
+       int best = 0;
+       int essential = 0;
+       int keep_deps = 0;
+       int keep_orphaned = 0;
+       int flags = 0;
+       int r;
+
+       const char* kwlist[] = {
+               "best",
+               "essential",
+               "keeps_deps",
+               "keep_orphaned",
+               NULL,
+       };
+
+       // Parse arguments
+       if (!PyArg_ParseTupleAndKeywords(args, kwargs, "s|bbbb", (char**)kwlist, &what,
+                       &best, &essential, &keep_deps, &keep_orphaned))
+               return NULL;
+
+       // Do we need to catch any invalid flags here and raise ValueError?
+
+       // Add request
+       r = pakfire_transaction_request(self->transaction, action, what, flags);
+       if (r < 0) {
+               errno = -r;
+               PyErr_SetFromErrno(PyExc_OSError);
+               return NULL;
+       }
+
+       Py_RETURN_NONE;
+}
+
+static PyObject* Transaction_install(TransactionObject* self, PyObject* args, PyObject* kwargs) {
+       return Transaction_request(self, PAKFIRE_JOB_INSTALL, args, kwargs);
+}
+
+static PyObject* Transaction_remove(TransactionObject* self, PyObject* args, PyObject* kwargs) {
+       return Transaction_request(self, PAKFIRE_JOB_ERASE, args, kwargs);
+}
+
+static PyObject* Transaction_update(TransactionObject* self, PyObject* args, PyObject* kwargs) {
+       return Transaction_request(self, PAKFIRE_JOB_UPDATE, args, kwargs);
+}
+
+static PyObject* Transaction_solve(TransactionObject* self) {
+       char* problems = NULL;
+       int r;
+
+       // Solve the transaction
+       r = pakfire_transaction_solve(self->transaction, PAKFIRE_SOLVE_SHOW_SOLUTIONS, &problems);
+       if (r < 0) {
+               errno = -r;
+               PyErr_SetFromErrno(PyExc_OSError);
+               goto ERROR;
+
+       // Solving failed :(
+       } else if (r > 0) {
+               PyErr_SetString(PyExc_DependencyError, problems);
+               goto ERROR;
+       }
+
+       // Problems should never be set here
+       assert(problems == NULL);
+
+       // Return None if everything was okay
+       Py_RETURN_NONE;
+
+ERROR:
+       if (problems)
+               free(problems);
+
+       return NULL;
+}
+
+static struct PyMethodDef Transaction_methods[] = {
+       {
+               "install",
+               (PyCFunction)Transaction_install,
+               METH_VARARGS|METH_KEYWORDS,
+               NULL,
+       },
+       {
+               "remove",
+               (PyCFunction)Transaction_remove,
+               METH_VARARGS|METH_KEYWORDS,
+               NULL,
+       },
+       {
+               "solve",
+               (PyCFunction)Transaction_solve,
+               METH_NOARGS,
+               NULL,
+       },
+       {
+               "update",
+               (PyCFunction)Transaction_update,
+               METH_VARARGS|METH_KEYWORDS,
+               NULL,
+       },
+       { NULL },
+};
+
+PyTypeObject TransactionType = {
+       PyVarObject_HEAD_INIT(NULL, 0)
+       .tp_name            = "pakfire.Transaction",
+       .tp_basicsize       = sizeof(TransactionObject),
+       .tp_flags           = Py_TPFLAGS_DEFAULT|Py_TPFLAGS_BASETYPE,
+       .tp_new             = Transaction_new,
+       .tp_dealloc         = (destructor)Transaction_dealloc,
+       .tp_init            = (initproc)Transaction_init,
+       .tp_doc             = "Transaction Object",
+       .tp_methods         = Transaction_methods,
+};
diff --git a/src/python/transaction.h b/src/python/transaction.h
new file mode 100644 (file)
index 0000000..88d8e09
--- /dev/null
@@ -0,0 +1,35 @@
+/*#############################################################################
+#                                                                             #
+# Pakfire - The IPFire package management system                              #
+# Copyright (C) 2025 Pakfire development team                                 #
+#                                                                             #
+# This program is free software: you can redistribute it and/or modify        #
+# it under the terms of the GNU General Public License as published by        #
+# the Free Software Foundation, either version 3 of the License, or           #
+# (at your option) any later version.                                         #
+#                                                                             #
+# This program is distributed in the hope that it will be useful,             #
+# but WITHOUT ANY WARRANTY; without even the implied warranty of              #
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the               #
+# GNU General Public License for more details.                                #
+#                                                                             #
+# You should have received a copy of the GNU General Public License           #
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.       #
+#                                                                             #
+#############################################################################*/
+
+#ifndef PYTHON_PAKFIRE_TRANSACTION_H
+#define PYTHON_PAKFIRE_TRANSACTION_H
+
+#include <Python.h>
+
+#include <pakfire/transaction.h>
+
+typedef struct {
+       PyObject_HEAD
+       struct pakfire_transaction* transaction;
+} TransactionObject;
+
+extern PyTypeObject TransactionType;
+
+#endif /* PYTHON_PAKFIRE_TRANSACTION_H */
diff --git a/tests/python/transaction.py b/tests/python/transaction.py
new file mode 100755 (executable)
index 0000000..2a1ce27
--- /dev/null
@@ -0,0 +1,47 @@
+#!/usr/bin/python3
+
+import pakfire
+import tests
+
+class TransactionTest(tests.TestCase):
+       """
+               This tests the Transaction class
+       """
+       def setUp(self):
+               self.pakfire = self.setup_pakfire()
+
+       def test_create(self):
+               # Create a new transaction
+               t = pakfire.Transaction(self.pakfire)
+
+               # Ensure we get a transaction
+               self.assertIsInstance(t, pakfire.Transaction)
+
+       def test_simple_solve(self):
+               """
+                       Performs some very simple operations which should all fail
+               """
+               t = pakfire.Transaction(self.pakfire)
+
+               # Install a few packages
+               t.install("a", essential=True)
+               t.install("b")
+               t.install("c")
+
+               # Try to remove some others
+               t.remove("d")
+               t.remove("e")
+               t.remove("f")
+
+               # And try to update something
+               t.update("g")
+               t.update("h")
+               t.update("i")
+
+               # Run the solver
+               with self.assertRaises(pakfire.DependencyError):
+                       t.solve()
+
+
+if __name__ == "__main__":
+       tests.main()