From: Michael Tremer Date: Fri, 14 Mar 2025 11:04:48 +0000 (+0000) Subject: python: Add a basic Transaction class X-Git-Url: http://git.ipfire.org/?a=commitdiff_plain;h=2753ec324e297c24b39884c26120cdf19eaf3862;p=pakfire.git python: Add a basic Transaction class Signed-off-by: Michael Tremer --- diff --git a/Makefile.am b/Makefile.am index 1a82140a..48319e9b 100644 --- a/Makefile.am +++ b/Makefile.am @@ -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 += \ diff --git a/src/python/pakfiremodule.c b/src/python/pakfiremodule.c index 4c2c3a7b..af7b9acb 100644 --- a/src/python/pakfiremodule.c +++ b/src/python/pakfiremodule.c @@ -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 index 00000000..8dfb7af6 --- /dev/null +++ b/src/python/transaction.c @@ -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 . # +# # +#############################################################################*/ + +#define PY_SSIZE_T_CLEAN +#include + +#include + +#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 index 00000000..88d8e098 --- /dev/null +++ b/src/python/transaction.h @@ -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 . # +# # +#############################################################################*/ + +#ifndef PYTHON_PAKFIRE_TRANSACTION_H +#define PYTHON_PAKFIRE_TRANSACTION_H + +#include + +#include + +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 index 00000000..2a1ce27d --- /dev/null +++ b/tests/python/transaction.py @@ -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()