From: Tomas Mlcoch Date: Wed, 15 May 2013 10:37:10 +0000 (+0200) Subject: python: Add binding + tests for xml_file module. X-Git-Tag: upstream/0.2.1~186 X-Git-Url: http://review.tizen.org/git/?a=commitdiff_plain;h=28b651d559777f2addadb4eba8230315e4e5935b;p=services%2Fcreaterepo_c.git python: Add binding + tests for xml_file module. --- diff --git a/src/python/CMakeLists.txt b/src/python/CMakeLists.txt index ed69687..e00c9a3 100644 --- a/src/python/CMakeLists.txt +++ b/src/python/CMakeLists.txt @@ -25,6 +25,7 @@ SET (craeterepo_cmodule_SRCS sqlite-py.c typeconversion.c xml_dump-py.c + xml_file-py.c ) ADD_LIBRARY(_createrepo_cmodule SHARED ${craeterepo_cmodule_SRCS}) diff --git a/src/python/__init__.py b/src/python/__init__.py index f1cae00..26e3e6d 100644 --- a/src/python/__init__.py +++ b/src/python/__init__.py @@ -28,7 +28,11 @@ DB_PRIMARY = _createrepo_c.DB_PRIMARY DB_FILELISTS = _createrepo_c.DB_FILELISTS DB_OTHER = _createrepo_c.DB_OTHER -CreaterepoCException = _createrepo_c.CreaterepoCException +XMLFILE_PRIMARY = _createrepo_c.XMLFILE_PRIMARY +XMLFILE_FILELISTS = _createrepo_c.XMLFILE_FILELISTS +XMLFILE_OTHER = _createrepo_c.XMLFILE_OTHER + +CreaterepoCError = _createrepo_c.CreaterepoCError # Metadata class @@ -75,6 +79,22 @@ class OtherSqlite(Sqlite): def __init__(self, filename): Sqlite.__init__(self, filename, DB_OTHER) +# XmlFile class + +XmlFile = _createrepo_c.XmlFile + +class PrimaryXmlFile(XmlFile): + def __init__(self, filename, compressiontype=GZ_COMPRESSION): + XmlFile.__init__(self, filename, XMLFILE_PRIMARY, compressiontype) + +class FilelistsXmlFile(XmlFile): + def __init__(self, filename, compressiontype=GZ_COMPRESSION): + XmlFile.__init__(self, filename, XMLFILE_FILELISTS, compressiontype) + +class OtherXmlFile(XmlFile): + def __init__(self, filename, compressiontype=GZ_COMPRESSION): + XmlFile.__init__(self, filename, XMLFILE_OTHER, compressiontype) + # Methods xml_dump_primary = _createrepo_c.xml_dump_primary diff --git a/src/python/createrepo_cmodule.c b/src/python/createrepo_cmodule.c index 500b042..e052386 100644 --- a/src/python/createrepo_cmodule.c +++ b/src/python/createrepo_cmodule.c @@ -30,6 +30,7 @@ #include "repomdrecord-py.h" #include "sqlite-py.h" #include "xml_dump-py.h" +#include "xml_file-py.h" static struct PyMethodDef createrepo_c_methods[] = { {"package_from_rpm", (PyCFunction)py_package_from_rpm, @@ -57,7 +58,7 @@ init_createrepo_c(void) /* Exceptions */ if (!init_exceptions()) return; - PyModule_AddObject(m, "CreaterepoCException", CrErr_Exception); + PyModule_AddObject(m, "CreaterepoCError", CrErr_Exception); /* Objects */ @@ -97,6 +98,12 @@ init_createrepo_c(void) Py_INCREF(&Sqlite_Type); PyModule_AddObject(m, "Sqlite", (PyObject *)&Sqlite_Type); + /* _createrepo_c.XmlFile */ + if (PyType_Ready(&XmlFile_Type) < 0) + return; + Py_INCREF(&XmlFile_Type); + PyModule_AddObject(m, "XmlFile", (PyObject *)&XmlFile_Type); + /* Createrepo init */ cr_xml_dump_init(); @@ -132,4 +139,9 @@ init_createrepo_c(void) PyModule_AddIntConstant(m, "DB_PRIMARY", CR_DB_PRIMARY); PyModule_AddIntConstant(m, "DB_FILELISTS", CR_DB_FILELISTS); PyModule_AddIntConstant(m, "DB_OTHER", CR_DB_OTHER); + + /* XmlFile types */ + PyModule_AddIntConstant(m, "XMLFILE_PRIMARY", CR_XMLFILE_PRIMARY); + PyModule_AddIntConstant(m, "XMLFILE_FILELISTS", CR_XMLFILE_FILELISTS); + PyModule_AddIntConstant(m, "XMLFILE_OTHER", CR_XMLFILE_OTHER); } diff --git a/src/python/xml_file-py.c b/src/python/xml_file-py.c new file mode 100644 index 0000000..8cc780c --- /dev/null +++ b/src/python/xml_file-py.c @@ -0,0 +1,271 @@ +/* createrepo_c - Library of routines for manipulation with repodata + * Copyright (C) 2013 Tomas Mlcoch + * + * 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 2 + * 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, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. + */ + +#include +#include +#include + +#include "xml_file-py.h" +#include "package-py.h" +#include "exception-py.h" +#include "typeconversion.h" + +typedef struct { + PyObject_HEAD + cr_XmlFile *xmlfile; +} _XmlFileObject; + +static PyObject * xmlfile_close(_XmlFileObject *self, void *nothing); + +static int +check_XmlFileStatus(const _XmlFileObject *self) +{ + assert(self != NULL); + assert(XmlFileObject_Check(self)); + if (self->xmlfile == NULL) { + PyErr_SetString(CrErr_Exception, + "Improper createrepo_c XmlFile object (Already closed file?)."); + return -1; + } + return 0; +} + +/* Function on the type */ + +static PyObject * +xmlfile_new(PyTypeObject *type, PyObject *args, PyObject *kwds) +{ + CR_UNUSED(args); + CR_UNUSED(kwds); + _XmlFileObject *self = (_XmlFileObject *)type->tp_alloc(type, 0); + if (self) + self->xmlfile = NULL; + return (PyObject *)self; +} + +static int +xmlfile_init(_XmlFileObject *self, PyObject *args, PyObject *kwds) +{ + char *path; + int type, comtype; + GError *err = NULL; + PyObject *ret; + + CR_UNUSED(kwds); + + if (!PyArg_ParseTuple(args, "sii|:xmlfile_init", &path, &type, &comtype)) + return -1; + + /* Check arguments */ + if (type < 0 || type >= CR_XMLFILE_SENTINEL) { + PyErr_SetString(PyExc_ValueError, "Unknown XML file type"); + return -1; + } + + if (comtype < 0 || comtype >= CR_CW_COMPRESSION_SENTINEL) { + PyErr_SetString(PyExc_ValueError, "Unknown compression type"); + return -1; + } + + /* Free all previous resources when reinitialization */ + ret = xmlfile_close(self, NULL); + Py_XDECREF(ret); + if (ret == NULL) { + // Error encountered! + return -1; + } + + /* Init */ + self->xmlfile = cr_xmlfile_open(path, type, comtype, &err); + if (err) { + PyErr_Format(CrErr_Exception, "XmlFile initialization failed: %s", err->message); + g_clear_error(&err); + return -1; + } + + return 0; +} + +static void +xmlfile_dealloc(_XmlFileObject *self) +{ + cr_xmlfile_close(self->xmlfile, NULL); + Py_TYPE(self)->tp_free(self); +} + +static PyObject * +xmlfile_repr(_XmlFileObject *self) +{ + char *type; + + switch (self->xmlfile->type) { + case CR_XMLFILE_PRIMARY: + type = "Primary"; + break; + case CR_XMLFILE_FILELISTS: + type = "Filelists"; + break; + case CR_XMLFILE_OTHER: + type = "Other"; + break; + default: + type = "Unknown"; + } + + return PyString_FromFormat("", type); +} + +/* XmlFile methods */ + +static PyObject * +set_num_of_pkgs(_XmlFileObject *self, PyObject *args) +{ + long num; + GError *err = NULL; + + if (!PyArg_ParseTuple(args, "l:set_num_of_pkgs", &num)) + return NULL; + + if (check_XmlFileStatus(self)) + return NULL; + + cr_xmlfile_set_num_of_pkgs(self->xmlfile, num, &err); + if (err) { + PyErr_Format(CrErr_Exception, "XmlFile set_num_of_pkgs error: %s", err->message); + g_clear_error(&err); + return NULL; + } + + Py_RETURN_NONE; +} + +static PyObject * +add_pkg(_XmlFileObject *self, PyObject *args) +{ + PyObject *py_pkg; + GError *err = NULL; + + if (!PyArg_ParseTuple(args, "O!:add_pkg", &Package_Type, &py_pkg)) + return NULL; + + if (check_XmlFileStatus(self)) + return NULL; + + cr_xmlfile_add_pkg(self->xmlfile, Package_FromPyObject(py_pkg), &err); + if (err) { + PyErr_Format(CrErr_Exception, "Cannot add package: %s", err->message); + g_clear_error(&err); + return NULL; + } + + Py_RETURN_NONE; +} + +static PyObject * +add_chunk(_XmlFileObject *self, PyObject *args) +{ + char *chunk; + GError *err = NULL; + + if (!PyArg_ParseTuple(args, "s:add_chunk", &chunk)) + return NULL; + + if (check_XmlFileStatus(self)) + return NULL; + + cr_xmlfile_add_chunk(self->xmlfile, chunk, &err); + if (err) { + PyErr_Format(CrErr_Exception, "Cannot add chunk: %s", err->message); + g_clear_error(&err); + return NULL; + } + + Py_RETURN_NONE; +} + +static PyObject * +xmlfile_close(_XmlFileObject *self, void *nothing) +{ + GError *err = NULL; + + CR_UNUSED(nothing); + + if (self->xmlfile) { + cr_xmlfile_close(self->xmlfile, &err); + self->xmlfile = NULL; + if (err) { + PyErr_Format(CrErr_Exception, "Error while closing: %s", err->message); + g_clear_error(&err); + return NULL; + } + } + Py_RETURN_NONE; +} + +static struct PyMethodDef xmlfile_methods[] = { + {"set_num_of_pkgs", (PyCFunction)set_num_of_pkgs, METH_VARARGS, NULL}, + {"add_pkg", (PyCFunction)add_pkg, METH_VARARGS, NULL}, + {"add_chunk", (PyCFunction)add_chunk, METH_VARARGS, NULL}, + {"close", (PyCFunction)xmlfile_close, METH_NOARGS, NULL}, + {NULL} /* sentinel */ +}; + +PyTypeObject XmlFile_Type = { + PyObject_HEAD_INIT(NULL) + 0, /* ob_size */ + "_librepo.XmlFile", /* tp_name */ + sizeof(_XmlFileObject), /* tp_basicsize */ + 0, /* tp_itemsize */ + (destructor) xmlfile_dealloc, /* tp_dealloc */ + 0, /* tp_print */ + 0, /* tp_getattr */ + 0, /* tp_setattr */ + 0, /* tp_compare */ + (reprfunc) xmlfile_repr, /* tp_repr */ + 0, /* tp_as_number */ + 0, /* tp_as_sequence */ + 0, /* tp_as_mapping */ + 0, /* tp_hash */ + 0, /* tp_call */ + 0, /* tp_str */ + 0, /* tp_getattro */ + 0, /* tp_setattro */ + 0, /* tp_as_buffer */ + Py_TPFLAGS_DEFAULT|Py_TPFLAGS_BASETYPE, /* tp_flags */ + "XmlFile object", /* tp_doc */ + 0, /* tp_traverse */ + 0, /* tp_clear */ + 0, /* tp_richcompare */ + 0, /* tp_weaklistoffset */ + PyObject_SelfIter, /* tp_iter */ + 0, /* tp_iternext */ + xmlfile_methods, /* tp_methods */ + 0, /* tp_members */ + 0, /* tp_getset */ + 0, /* tp_base */ + 0, /* tp_dict */ + 0, /* tp_descr_get */ + 0, /* tp_descr_set */ + 0, /* tp_dictoffset */ + (initproc) xmlfile_init, /* tp_init */ + 0, /* tp_alloc */ + xmlfile_new, /* tp_new */ + 0, /* tp_free */ + 0, /* tp_is_gc */ +}; diff --git a/src/python/xml_file-py.h b/src/python/xml_file-py.h new file mode 100644 index 0000000..d7afcc4 --- /dev/null +++ b/src/python/xml_file-py.h @@ -0,0 +1,29 @@ +/* createrepo_c - Library of routines for manipulation with repodata + * Copyright (C) 2013 Tomas Mlcoch + * + * 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 2 + * 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, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. + */ + +#ifndef CR_XML_FILE_PY_H +#define CR_XML_FILE_PY_H + +#include "src/createrepo_c.h" + +extern PyTypeObject XmlFile_Type; + +#define XmlFileObject_Check(o) PyObject_TypeCheck(o, &XmlFile_Type) + +#endif diff --git a/src/xml_file.c b/src/xml_file.c index 2e0e661..be0eea6 100644 --- a/src/xml_file.c +++ b/src/xml_file.c @@ -234,9 +234,11 @@ cr_xmlfile_close(cr_XmlFile *f, GError **err) { GError *tmp_err = NULL; - assert(f); assert(!err || *err == NULL); + if (!f) + return CRE_OK; + if (f->header == 0) { cr_xmlfile_write_xml_header(f, &tmp_err); if (tmp_err) { diff --git a/tests/python/tests/test_xml_file.py b/tests/python/tests/test_xml_file.py new file mode 100644 index 0000000..6120853 --- /dev/null +++ b/tests/python/tests/test_xml_file.py @@ -0,0 +1,309 @@ +import unittest +import shutil +import tempfile +import os.path +import createrepo_c as cr + +from fixtures import * + +class TestCaseXmlFile(unittest.TestCase): + + def setUp(self): + self.tmpdir = tempfile.mkdtemp(prefix="createrepo_ctest-") + + def tearDown(self): + shutil.rmtree(self.tmpdir) + + def test_xmlfile_basic_operations(self): + pri = cr.XmlFile(self.tmpdir+"/primary.xml.gz", + cr.XMLFILE_PRIMARY, + cr.GZ_COMPRESSION) + self.assertTrue(pri) + self.assertTrue(os.path.isfile(self.tmpdir+"/primary.xml.gz")) + + fil = cr.XmlFile(self.tmpdir+"/filelists.xml.gz", + cr.XMLFILE_FILELISTS, + cr.GZ_COMPRESSION) + self.assertTrue(fil) + self.assertTrue(os.path.isfile(self.tmpdir+"/filelists.xml.gz")) + + oth = cr.XmlFile(self.tmpdir+"/other.xml.gz", + cr.XMLFILE_OTHER, + cr.GZ_COMPRESSION) + self.assertTrue(oth) + self.assertTrue(os.path.isfile(self.tmpdir+"/other.xml.gz")) + + def test_xmlfile_operations_on_closed_file(self): + # Already closed file + path = os.path.join(self.tmpdir, "primary.xml.gz") + pkg = cr.package_from_rpm(PKG_ARCHER_PATH) + self.assertTrue(pkg) + + f = cr.PrimaryXmlFile(path, cr.GZ_COMPRESSION) + self.assertTrue(f) + self.assertTrue(os.path.isfile(path)) + f.close() + self.assertRaises(cr.CreaterepoCError, f.set_num_of_pkgs, 1) + self.assertRaises(cr.CreaterepoCError, f.add_pkg, pkg) + self.assertRaises(cr.CreaterepoCError, f.add_chunk, "text") + f.close() # No error should be raised + del(f) # No error should be raised + + def test_xmlfile_error_cases(self): + path = os.path.join(self.tmpdir, "foofile") + self.assertFalse(os.path.exists(path)) + + # Bad file type + self.assertRaises(ValueError, cr.XmlFile, path, 86, cr.GZ_COMPRESSION) + self.assertFalse(os.path.exists(path)) + + # Bad compression type + self.assertRaises(ValueError, cr.XmlFile, path, cr.XMLFILE_PRIMARY, 678) + self.assertFalse(os.path.exists(path)) + + # Non existing path + self.assertRaises(cr.CreaterepoCError, cr.PrimaryXmlFile, + "foobar/foo/xxx/cvydmaticxuiowe") + + # Already existing file + open(path, "w").write("foobar") + self.assertRaises(cr.CreaterepoCError, cr.PrimaryXmlFile, path) + + def test_xmlfile_no_compression(self): + path = os.path.join(self.tmpdir, "primary.xml") + f = cr.PrimaryXmlFile(path, cr.NO_COMPRESSION) + self.assertTrue(f) + self.assertTrue(os.path.isfile(path)) + f.close() + + content = open(path).read() + self.assertEqual(content, +""" + +""") + + def test_xmlfile_gz_compression(self): + path = os.path.join(self.tmpdir, "primary.xml.gz") + f = cr.PrimaryXmlFile(path, cr.GZ_COMPRESSION) + self.assertTrue(f) + self.assertTrue(os.path.isfile(path)) + f.close() + + import gzip + content = gzip.open(path).read() + self.assertEqual(content, +""" + +""") + + def test_xmlfile_bz2_compression(self): + path = os.path.join(self.tmpdir, "primary.xml.bz2") + f = cr.PrimaryXmlFile(path, cr.BZ2_COMPRESSION) + self.assertTrue(f) + self.assertTrue(os.path.isfile(path)) + f.close() + + import bz2 + content = bz2.decompress(open(path).read()) + self.assertEqual(content, +""" + +""") + + def test_xmlfile_xz_compression(self): + path = os.path.join(self.tmpdir, "primary.xml.xz") + f = cr.PrimaryXmlFile(path, cr.XZ_COMPRESSION) + self.assertTrue(f) + self.assertTrue(os.path.isfile(path)) + f.close() + + import subprocess + p = subprocess.Popen(["unxz", "--stdout", path], stdout=subprocess.PIPE) + content = p.stdout.read() + self.assertEqual(content, +""" + +""") + + def test_xmlfile_add_pkg(self): + pkg = cr.package_from_rpm(PKG_ARCHER_PATH) + self.assertTrue(pkg) + + # Primary + path = os.path.join(self.tmpdir, "primary.xml") + f = cr.PrimaryXmlFile(path, cr.NO_COMPRESSION) + self.assertTrue(f) + self.assertTrue(os.path.isfile(path)) + f.add_pkg(pkg) + self.assertRaises(TypeError, f.add_pkg, None) + self.assertRaises(TypeError, f.add_pkg, 123) + self.assertRaises(TypeError, f.add_pkg, "foo") + self.assertRaises(TypeError, f.add_pkg, [456]) + f.close() + + self.assertTrue(os.path.isfile(path)) + self.assertEqual(open(path).read(), +""" + + + Archer + x86_64 + + 4e0b775220c67f0f2c1fd2177e626b9c863a098130224ff09778ede25cea9a9e + Complex package. + Archer package + Sterling Archer + http://soo_complex_package.eu/ + +""") + + # Filelists + path = os.path.join(self.tmpdir, "filelists.xml") + f = cr.FilelistsXmlFile(path, cr.NO_COMPRESSION) + self.assertTrue(f) + self.assertTrue(os.path.isfile(path)) + f.add_pkg(pkg) + self.assertRaises(TypeError, f.add_pkg, None) + self.assertRaises(TypeError, f.add_pkg, 123) + self.assertRaises(TypeError, f.add_pkg, "foo") + self.assertRaises(TypeError, f.add_pkg, [456]) + f.close() + + self.assertTrue(os.path.isfile(path)) + self.assertEqual(open(path).read(), +""" + + + + /usr/bin/complex_a + /usr/share/doc/Archer-3.4.5 + /usr/share/doc/Archer-3.4.5/README + +""") + + # Other + path = os.path.join(self.tmpdir, "other.xml") + f = cr.OtherXmlFile(path, cr.NO_COMPRESSION) + self.assertTrue(f) + self.assertTrue(os.path.isfile(path)) + f.add_pkg(pkg) + self.assertRaises(TypeError, f.add_pkg, None) + self.assertRaises(TypeError, f.add_pkg, 123) + self.assertRaises(TypeError, f.add_pkg, "foo") + self.assertRaises(TypeError, f.add_pkg, [456]) + f.close() + + self.assertTrue(os.path.isfile(path)) + self.assertEqual(open(path).read(), +""" + + + + - First changelog. + - That was totally ninja! + - 3. changelog. + +""") + + def test_xmlfile_add_chunk(self): + chunk = " Some XML chunk\n" + + # Primary + path = os.path.join(self.tmpdir, "primary.xml") + f = cr.PrimaryXmlFile(path, cr.NO_COMPRESSION) + self.assertTrue(f) + self.assertTrue(os.path.isfile(path)) + f.add_chunk(chunk) + self.assertRaises(TypeError, f.add_chunk, None) + self.assertRaises(TypeError, f.add_chunk, 123) + self.assertRaises(TypeError, f.add_chunk, [1]) + self.assertRaises(TypeError, f.add_chunk, ["foo"]) + f.close() + + self.assertTrue(os.path.isfile(path)) + self.assertEqual(open(path).read(), +""" + + Some XML chunk +""") + + # Filelists + path = os.path.join(self.tmpdir, "filelists.xml") + f = cr.FilelistsXmlFile(path, cr.NO_COMPRESSION) + self.assertTrue(f) + self.assertTrue(os.path.isfile(path)) + f.add_chunk(chunk) + self.assertRaises(TypeError, f.add_chunk, None) + self.assertRaises(TypeError, f.add_chunk, 123) + self.assertRaises(TypeError, f.add_chunk, [1]) + self.assertRaises(TypeError, f.add_chunk, ["foo"]) + f.close() + + self.assertTrue(os.path.isfile(path)) + self.assertEqual(open(path).read(), +""" + + Some XML chunk +""") + + # Other + path = os.path.join(self.tmpdir, "other.xml") + f = cr.OtherXmlFile(path, cr.NO_COMPRESSION) + self.assertTrue(f) + self.assertTrue(os.path.isfile(path)) + f.add_chunk(chunk) + self.assertRaises(TypeError, f.add_chunk, None) + self.assertRaises(TypeError, f.add_chunk, 123) + self.assertRaises(TypeError, f.add_chunk, [1]) + self.assertRaises(TypeError, f.add_chunk, ["foo"]) + f.close() + + self.assertTrue(os.path.isfile(path)) + self.assertEqual(open(path).read(), +""" + + Some XML chunk +""")