From: Tomas Mlcoch Date: Thu, 13 Jun 2013 11:02:58 +0000 (+0200) Subject: python: Add CrFile class (Bindings for the CR_FILE object). X-Git-Tag: upstream/0.2.1~82 X-Git-Url: http://review.tizen.org/git/?a=commitdiff_plain;h=efc6aa89fa0e1ea2faad6b4d5e2d689fa6494b49;p=services%2Fcreaterepo_c.git python: Add CrFile class (Bindings for the CR_FILE object). --- diff --git a/src/python/CMakeLists.txt b/src/python/CMakeLists.txt index 16f9eb8..9658d0b 100644 --- a/src/python/CMakeLists.txt +++ b/src/python/CMakeLists.txt @@ -14,6 +14,7 @@ FILE(COPY ${createrepo_c_COPIES} DESTINATION ./createrepo_c/) SET (craeterepo_cmodule_SRCS checksum-py.c + compression_wrapper-py.c contentstat-py.c createrepo_cmodule.c exception-py.c diff --git a/src/python/__init__.py b/src/python/__init__.py index 3952542..dd08405 100644 --- a/src/python/__init__.py +++ b/src/python/__init__.py @@ -18,6 +18,9 @@ SHA256 = _createrepo_c.SHA256 SHA384 = _createrepo_c.SHA384 SHA512 = _createrepo_c.SHA512 +MODE_READ = _createrepo_c.MODE_READ +MODE_WRITE = _createrepo_c.MODE_WRITE + AUTO_DETECT_COMPRESSION = _createrepo_c.AUTO_DETECT_COMPRESSION UNKNOWN_COMPRESSION = _createrepo_c.UNKNOWN_COMPRESSION NO_COMPRESSION = _createrepo_c.NO_COMPRESSION @@ -52,6 +55,13 @@ CreaterepoCError = _createrepo_c.CreaterepoCError ContentStat = _createrepo_c.ContentStat +# CrFile class + +class CrFile(_createrepo_c.CrFile): + def __init__(self, filename, mode=MODE_READ, + comtype=NO_COMPRESSION, stat=None): + _createrepo_c.CrFile.__init__(self, filename, mode, comtype, stat) + # Metadata class Metadata = _createrepo_c.Metadata diff --git a/src/python/compression_wrapper-py.c b/src/python/compression_wrapper-py.c new file mode 100644 index 0000000..02807e1 --- /dev/null +++ b/src/python/compression_wrapper-py.c @@ -0,0 +1,248 @@ +/* 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 "compression_wrapper-py.h" +#include "exception-py.h" +#include "contentstat-py.h" +#include "typeconversion.h" + +typedef struct { + PyObject_HEAD + CR_FILE *f; + PyObject *py_stat; +} _CrFileObject; + +static PyObject * py_close(_CrFileObject *self, void *nothing); + +static int +check_CrFileStatus(const _CrFileObject *self) +{ + assert(self != NULL); + assert(CrFileObject_Check(self)); + if (self->f == NULL) { + PyErr_SetString(CrErr_Exception, + "Improper createrepo_c CrFile object (Already closed file?)."); + return -1; + } + return 0; +} + +/* Function on the type */ + +static PyObject * +crfile_new(PyTypeObject *type, PyObject *args, PyObject *kwds) +{ + CR_UNUSED(args); + CR_UNUSED(kwds); + _CrFileObject *self = (_CrFileObject *)type->tp_alloc(type, 0); + if (self) { + self->f = NULL; + self->py_stat = NULL; + } + return (PyObject *)self; +} + +static int +crfile_init(_CrFileObject *self, PyObject *args, PyObject *kwds) +{ + char *path; + int mode, comtype; + GError *err = NULL; + PyObject *py_stat, *ret; + cr_ContentStat *stat; + + CR_UNUSED(kwds); + + if (!PyArg_ParseTuple(args, "siiO|:crfile_init", + &path, &mode, &comtype, &py_stat)) + return -1; + + /* Check arguments */ + if (mode != CR_CW_MODE_READ && mode != CR_CW_MODE_WRITE) { + PyErr_SetString(PyExc_ValueError, "Bad open mode"); + return -1; + } + + if (comtype < 0 || comtype >= CR_CW_COMPRESSION_SENTINEL) { + PyErr_SetString(PyExc_ValueError, "Unknown compression type"); + return -1; + } + + if (py_stat == Py_None) { + stat = NULL; + } else if (ContentStatObject_Check(py_stat)) { + stat = ContentStat_FromPyObject(py_stat); + } else { + PyErr_SetString(PyExc_ValueError, "Use ContentStat or None"); + return -1; + } + + /* Free all previous resources when reinitialization */ + ret = py_close(self, NULL); + Py_XDECREF(ret); + Py_XDECREF(self->py_stat); + self->py_stat = NULL; + if (ret == NULL) { + // Error encountered! + return -1; + } + + /* Init */ + self->f = cr_open_with_stat(path, mode, comtype, stat, &err); + if (err) { + PyErr_Format(CrErr_Exception, "CrFile initialization failed: %s", err->message); + g_clear_error(&err); + return -1; + } + + self->py_stat = py_stat; + Py_XINCREF(py_stat); + + return 0; +} + +static void +crfile_dealloc(_CrFileObject *self) +{ + cr_close(self->f, NULL); + Py_XDECREF(self->py_stat); + Py_TYPE(self)->tp_free(self); +} + +static PyObject * +crfile_repr(_CrFileObject *self) +{ + char *mode; + + switch (self->f->mode) { + case CR_CW_MODE_READ: + mode = "Read mode"; + break; + case CR_CW_MODE_WRITE: + mode = "Write mode"; + break; + default: + mode = "Unknown mode"; + } + + return PyString_FromFormat("", mode); +} + +/* CrFile methods */ + +static PyObject * +py_write(_CrFileObject *self, PyObject *args) +{ + char *str; + Py_ssize_t len; + GError *tmp_err = NULL; + + if (!PyArg_ParseTuple(args, "s#:set_num_of_pkgs", &str, &len)) + return NULL; + + if (check_CrFileStatus(self)) + return NULL; + + cr_write(self->f, str, len, &tmp_err); + if (tmp_err) { + PyErr_Format(CrErr_Exception, "%s", tmp_err->message); + g_clear_error(&tmp_err); + return NULL; + } + + Py_RETURN_NONE; +} + +static PyObject * +py_close(_CrFileObject *self, void *nothing) +{ + GError *tmp_err = NULL; + + CR_UNUSED(nothing); + + if (self->f) { + cr_close(self->f, &tmp_err); + self->f = NULL; + } + + Py_XDECREF(self->py_stat); + self->py_stat = NULL; + + if (tmp_err) { + PyErr_Format(CrErr_Exception, "Close error: %s", tmp_err->message); + g_clear_error(&tmp_err); + return NULL; + } + + Py_RETURN_NONE; +} + +static struct PyMethodDef crfile_methods[] = { + {"write", (PyCFunction)py_write, METH_VARARGS, NULL}, + {"close", (PyCFunction)py_close, METH_NOARGS, NULL}, + {NULL} /* sentinel */ +}; + +PyTypeObject CrFile_Type = { + PyObject_HEAD_INIT(NULL) + 0, /* ob_size */ + "createrepo_c.CrFile", /* tp_name */ + sizeof(_CrFileObject), /* tp_basicsize */ + 0, /* tp_itemsize */ + (destructor) crfile_dealloc, /* tp_dealloc */ + 0, /* tp_print */ + 0, /* tp_getattr */ + 0, /* tp_setattr */ + 0, /* tp_compare */ + (reprfunc) crfile_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 */ + "CrFile object", /* tp_doc */ + 0, /* tp_traverse */ + 0, /* tp_clear */ + 0, /* tp_richcompare */ + 0, /* tp_weaklistoffset */ + PyObject_SelfIter, /* tp_iter */ + 0, /* tp_iternext */ + crfile_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) crfile_init, /* tp_init */ + 0, /* tp_alloc */ + crfile_new, /* tp_new */ + 0, /* tp_free */ + 0, /* tp_is_gc */ +}; diff --git a/src/python/compression_wrapper-py.h b/src/python/compression_wrapper-py.h new file mode 100644 index 0000000..81ebe2f --- /dev/null +++ b/src/python/compression_wrapper-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_COMPRESSION_WRAPPER_PY_H +#define CR_COMPRESSION_WRAPPER_PY_H + +#include "src/createrepo_c.h" + +extern PyTypeObject CrFile_Type; + +#define CrFileObject_Check(o) PyObject_TypeCheck(o, &CrFile_Type) + +#endif diff --git a/src/python/createrepo_cmodule.c b/src/python/createrepo_cmodule.c index 8b05779..24d62a8 100644 --- a/src/python/createrepo_cmodule.c +++ b/src/python/createrepo_cmodule.c @@ -22,6 +22,7 @@ #include "src/createrepo_c.h" #include "checksum-py.h" +#include "compression_wrapper-py.h" #include "contentstat-py.h" #include "exception-py.h" #include "load_metadata-py.h" @@ -86,6 +87,12 @@ init_createrepo_c(void) Py_INCREF(&ContentStat_Type); PyModule_AddObject(m, "ContentStat", (PyObject *)&ContentStat_Type); + /* _createrepo_c.CrFile */ + if (PyType_Ready(&CrFile_Type) < 0) + return; + Py_INCREF(&CrFile_Type); + PyModule_AddObject(m, "CrFile", (PyObject *)&CrFile_Type); + /* _createrepo_c.Package */ if (PyType_Ready(&Package_Type) < 0) return; @@ -153,6 +160,10 @@ init_createrepo_c(void) PyModule_AddIntConstant(m, "SHA384", CR_CHECKSUM_SHA384); PyModule_AddIntConstant(m, "SHA512", CR_CHECKSUM_SHA512); + /* File open modes */ + PyModule_AddIntConstant(m, "MODE_READ", CR_CW_MODE_READ); + PyModule_AddIntConstant(m, "MODE_WRITE", CR_CW_MODE_WRITE); + /* Compression types */ PyModule_AddIntConstant(m, "AUTO_DETECT_COMPRESSION", CR_CW_AUTO_DETECT_COMPRESSION); PyModule_AddIntConstant(m, "UNKNOWN_COMPRESSION", CR_CW_UNKNOWN_COMPRESSION); diff --git a/tests/python/tests/test_contentstat.py b/tests/python/tests/test_contentstat.py index 17be652..eb08f5d 100644 --- a/tests/python/tests/test_contentstat.py +++ b/tests/python/tests/test_contentstat.py @@ -43,7 +43,7 @@ class TestCaseContentStat(unittest.TestCase): self.assertEqual(cs.checksum, "67bc6282915fad80dc11f3d7c3210977a0bde"\ "05a762256d86083c2447d425776") - def test_contentstat_2(self): + def test_contentstat_ref_in_xmlfile(self): """Test if reference is saved properly""" pkg = cr.package_from_rpm(PKG_ARCHER_PATH) @@ -66,3 +66,21 @@ class TestCaseContentStat(unittest.TestCase): f.close() self.assertTrue(os.path.isfile(path)) + + def test_contentstat_ref_in_crfile(self): + """Test if reference is saved properly""" + + cs = cr.ContentStat(cr.SHA256) + self.assertEqual(cs.size, 0) + self.assertEqual(cs.checksum_type, cr.SHA256) + self.assertEqual(cs.checksum, None) + + path = os.path.join(self.tmpdir, "foofile.gz") + f = cr.CrFile(path, cr.MODE_WRITE, cr.GZ_COMPRESSION, cs) + self.assertTrue(f) + self.assertTrue(os.path.isfile(path)) + del cs + f.write("foobar") + f.close() + + self.assertTrue(os.path.isfile(path)) diff --git a/tests/python/tests/test_crfile.py b/tests/python/tests/test_crfile.py new file mode 100644 index 0000000..f2511e5 --- /dev/null +++ b/tests/python/tests/test_crfile.py @@ -0,0 +1,106 @@ +import unittest +import shutil +import tempfile +import os.path +import createrepo_c as cr + +from fixtures import * + +class TestCaseCrFile(unittest.TestCase): + + def setUp(self): + self.tmpdir = tempfile.mkdtemp(prefix="createrepo_ctest-") + + def tearDown(self): + shutil.rmtree(self.tmpdir) + + def test_crfile_basic_operations(self): + f = cr.CrFile(self.tmpdir+"/foo.gz", + cr.MODE_WRITE, + cr.GZ_COMPRESSION, + None) + self.assertTrue(f) + self.assertTrue(os.path.isfile(self.tmpdir+"/foo.gz")) + + def test_crfile_operations_on_closed_file(self): + # Already closed file + path = os.path.join(self.tmpdir, "primary.xml.gz") + f = cr.CrFile(path, cr.MODE_WRITE, cr.GZ_COMPRESSION) + self.assertTrue(f) + self.assertTrue(os.path.isfile(path)) + f.close() + + self.assertRaises(cr.CreaterepoCError, f.write, "foobar") + f.close() # No error should be raised + del(f) # No error should be raised + + def test_crfile_error_cases(self): + path = os.path.join(self.tmpdir, "foofile") + self.assertFalse(os.path.exists(path)) + + # Bad open mode + self.assertRaises(ValueError, cr.CrFile, path, 86, + cr.GZ_COMPRESSION, None) + self.assertFalse(os.path.exists(path)) + + # Bad compression type + self.assertRaises(ValueError, cr.CrFile, path, + cr.MODE_READ, 678, None) + self.assertFalse(os.path.exists(path)) + + # Bad contentstat object + self.assertRaises(ValueError, cr.XmlFile, path, + cr.MODE_READ, cr.GZ_COMPRESSION, "foo") + self.assertFalse(os.path.exists(path)) + + # Non existing path + self.assertRaises(cr.CreaterepoCError, cr.CrFile, + "foobar/foo/xxx/cvydmaticxuiowe") + + def test_crfile_no_compression(self): + path = os.path.join(self.tmpdir, "foo") + f = cr.CrFile(path, cr.MODE_WRITE, cr.NO_COMPRESSION) + self.assertTrue(f) + self.assertTrue(os.path.isfile(path)) + f.write("foobar") + f.close() + + content = open(path).read() + self.assertEqual(content, "foobar") + + def test_crfile_gz_compression(self): + path = os.path.join(self.tmpdir, "foo.gz") + f = cr.CrFile(path, cr.MODE_WRITE, cr.GZ_COMPRESSION) + self.assertTrue(f) + self.assertTrue(os.path.isfile(path)) + f.write("foobar") + f.close() + + import gzip + content = gzip.open(path).read() + self.assertEqual(content, "foobar") + + def test_crfile_bz2_compression(self): + path = os.path.join(self.tmpdir, "foo.bz2") + f = cr.CrFile(path, cr.MODE_WRITE, cr.BZ2_COMPRESSION) + self.assertTrue(f) + self.assertTrue(os.path.isfile(path)) + f.write("foobar") + f.close() + + import bz2 + content = bz2.decompress(open(path).read()) + self.assertEqual(content, "foobar") + + def test_crfile_xz_compression(self): + path = os.path.join(self.tmpdir, "foo.xz") + f = cr.CrFile(path, cr.MODE_WRITE, cr.XZ_COMPRESSION) + self.assertTrue(f) + self.assertTrue(os.path.isfile(path)) + f.write("foobar") + f.close() + + import subprocess + p = subprocess.Popen(["unxz", "--stdout", path], stdout=subprocess.PIPE) + content = p.stdout.read() + self.assertEqual(content, "foobar")