From c5e0e8138ca58cfe2ed1f362d2e4da74ce1c4c72 Mon Sep 17 00:00:00 2001
From: Andre Anjos <andre.dos.anjos@gmail.com>
Date: Thu, 14 Nov 2013 18:21:04 +0100
Subject: [PATCH] Append/Set/Replace implemented; More tests passing

---
 xbob/io/bobskin.h         |   6 +-
 xbob/io/hdf5.cpp          | 655 +++++++++++++++++++++++++++++++++++---
 xbob/io/test/test_hdf5.py |  12 +-
 3 files changed, 611 insertions(+), 62 deletions(-)

diff --git a/xbob/io/bobskin.h b/xbob/io/bobskin.h
index bede271..8a9fe7b 100644
--- a/xbob/io/bobskin.h
+++ b/xbob/io/bobskin.h
@@ -22,17 +22,17 @@ class bobskin: public bob::core::array::interface {
   public: //api
 
     /**
-     * @brief Builds a new array an array like object
+     * @brief Builds a new skin from an array like object
      */
     bobskin(PyObject* array, bob::core::array::ElementType eltype);
 
     /**
-     * @brief Builds a new array an array like object
+     * @brief Builds a new skin from a numpy array object
      */
     bobskin(PyArrayObject* array, bob::core::array::ElementType eltype);
 
     /**
-     * @brief Builds a new array an array like object
+     * @brief Builds a new skin around a blitz array object
      */
     bobskin(PyBlitzArrayObject* array);
 
diff --git a/xbob/io/hdf5.cpp b/xbob/io/hdf5.cpp
index 9c57816..e578074 100644
--- a/xbob/io/hdf5.cpp
+++ b/xbob/io/hdf5.cpp
@@ -288,51 +288,60 @@ If the given path is relative, it is take w.r.t. to the current\n\
 working directory.\n\
 ");
 
+static bob::io::hdf5type PyBobIo_H5FromTypenum (int type_num) {
+
+  switch(type_num) {
+    case NPY_STRING:     return bob::io::s;
+    case NPY_BOOL:       return bob::io::b;
+    case NPY_INT8:       return bob::io::i8;
+    case NPY_INT16:      return bob::io::i16;
+    case NPY_INT32:      return bob::io::i32;
+    case NPY_INT64:      return bob::io::i64;
+    case NPY_UINT8:      return bob::io::u8;
+    case NPY_UINT16:     return bob::io::u16;
+    case NPY_UINT32:     return bob::io::u32;
+    case NPY_UINT64:     return bob::io::u64;
+    case NPY_FLOAT32:    return bob::io::f32;
+    case NPY_FLOAT64:    return bob::io::f64;
+#ifdef NPY_FLOAT128
+    case NPY_FLOAT128:   return bob::io::f128;
+#endif
+    case NPY_COMPLEX64:  return bob::io::c64;
+    case NPY_COMPLEX128: return bob::io::c128;
+#ifdef NPY_COMPLEX256
+    case NPY_COMPLEX256: return bob::io::c256;
+#endif
+    default:             return bob::io::unsupported;
+  }
+
+}
+
 static int PyBobIo_H5AsTypenum (bob::io::hdf5type type) {
 
   switch(type) {
-    case bob::io::s:
-      return NPY_STRING;
-    case bob::io::b:
-      return NPY_BOOL;
-    case bob::io::i8:
-      return NPY_INT8;
-    case bob::io::i16:
-      return NPY_INT16;
-    case bob::io::i32:
-      return NPY_INT32;
-    case bob::io::i64:
-      return NPY_INT64;
-    case bob::io::u8:
-      return NPY_UINT8;
-    case bob::io::u16:
-      return NPY_UINT16;
-    case bob::io::u32:
-      return NPY_UINT32;
-    case bob::io::u64:
-      return NPY_UINT64;
-    case bob::io::f32:
-      return NPY_FLOAT32;
-    case bob::io::f64:
-      return NPY_FLOAT64;
+    case bob::io::s:    return NPY_STRING;
+    case bob::io::b:    return NPY_BOOL;
+    case bob::io::i8:   return NPY_INT8;
+    case bob::io::i16:  return NPY_INT16;
+    case bob::io::i32:  return NPY_INT32;
+    case bob::io::i64:  return NPY_INT64;
+    case bob::io::u8:   return NPY_UINT8;
+    case bob::io::u16:  return NPY_UINT16;
+    case bob::io::u32:  return NPY_UINT32;
+    case bob::io::u64:  return NPY_UINT64;
+    case bob::io::f32:  return NPY_FLOAT32;
+    case bob::io::f64:  return NPY_FLOAT64;
 #ifdef NPY_FLOAT128
-    case bob::io::f128:
-      return NPY_FLOAT128;
+    case bob::io::f128: return NPY_FLOAT128;
 #endif
-    case bob::io::c64:
-      return NPY_COMPLEX64;
-    case bob::io::c128:
-      return NPY_COMPLEX128;
+    case bob::io::c64:  return NPY_COMPLEX64;
+    case bob::io::c128: return NPY_COMPLEX128;
 #ifdef NPY_COMPLEX256
-    case bob::io::c256:
-      return NPY_COMPLEX256;
+    case bob::io::c256: return NPY_COMPLEX256;
 #endif
-    default:
-      PyErr_Format(PyExc_TypeError, "unsupported Bob/C++ element type (%s)", bob::io::stringize(type));
+    default:            return NPY_NOTYPE;
   }
 
-  return NPY_NOTYPE;
-
 }
 
 static PyObject* PyBobIo_HDF5TypeAsTuple (const bob::io::HDF5Type& t) {
@@ -341,7 +350,11 @@ static PyObject* PyBobIo_HDF5TypeAsTuple (const bob::io::HDF5Type& t) {
   const hsize_t* shptr = sh.get();
 
   int type_num = PyBobIo_H5AsTypenum(t.type());
-  if (type_num == NPY_NOTYPE) return 0;
+  if (type_num == NPY_NOTYPE) {
+    PyErr_Format(PyExc_TypeError, "unsupported HDF5 element type (%d) found during conversion to numpy type number", (int)t.type());
+    return 0;
+  }
+
   PyObject* dtype = reinterpret_cast<PyObject*>(PyArray_DescrFromType(type_num));
   if (!dtype) return 0;
 
@@ -689,20 +702,17 @@ static PyObject* PyBobIoHDF5File_Xread(PyBobIoHDF5FileObject* self,
   }
 
   //read as an numpy array
-  bob::core::array::typeinfo atype;
-  type.copy_to(atype);
-
-  int type_num = PyBobIo_AsTypenum(atype.dtype);
+  int type_num = PyBobIo_H5AsTypenum(type.type());
   if (type_num == NPY_NOTYPE) return 0; ///< failure
 
   npy_intp pyshape[NPY_MAXDIMS];
-  for (int k=0; k<atype.nd; ++k) pyshape[k] = atype.shape[k];
+  for (int k=0; k<shape.n(); ++k) pyshape[k] = shape.get()[k];
 
-  PyObject* retval = PyArray_SimpleNew(atype.nd, pyshape, type_num);
+  PyObject* retval = PyArray_SimpleNew(shape.n(), pyshape, type_num);
   if (!retval) return 0;
 
   try {
-    self->f->read_buffer(p, pos, atype, PyArray_DATA((PyArrayObject*)retval));
+    self->f->read_buffer(p, pos, type, PyArray_DATA((PyArrayObject*)retval));
   }
   catch (std::exception& e) {
     PyErr_SetString(PyExc_RuntimeError, e.what());
@@ -812,18 +822,313 @@ with N-1 dimensions. It returns a single\n\
 value >= 0, or a list of arrays otherwise.\n\
 ");
 
-static PyObject* PyBobIoHDF5File_Replace(PyBobIoHDF5FileObject* self, PyObject *args, PyObject* kwds) {
+/**
+ * Sets at 't', the type of the object 'o' according to our support types.
+ * Raise in case of problems. Furthermore, returns 'true' if the object is as
+ * simple scalar.
+ */
+
+static char* PyBobIo_GetString(PyObject* o) {
+#if PY_VERSION_HEX < 0x03000000
+  return PyString_AsString(o);
+#else
+  return PyBytes_AsString(o);
+#endif
+}
+
+static int PyBobIoHDF5File_SetStringType(bob::io::HDF5Type& t, PyObject* o) {
+  char* s = PyBobIo_GetString(o);
+  if (!s) return -1;
+  t = bob::io::HDF5Type(s);
+  return 0;
+}
+
+template <typename T> int PyBobIoHDF5File_SetType(bob::io::HDF5Type& t) {
+  T v;
+  t = bob::io::HDF5Type(v);
+  return 0;
+}
+
+/**
+ * A function to check for python scalars that works with numpy-1.6.x
+ */
+static bool PyBobIoHDF5File_IsPythonScalar(PyObject* obj) {
+  return (
+    PyBool_Check(obj) ||
+#if PY_VERSION_HEX < 0x03000000
+    PyString_Check(obj) ||
+#else
+    PyBytes_Check(obj) ||
+#endif
+    PyUnicode_Check(obj) ||
+#if PY_VERSION_HEX < 0x03000000
+    PyInt_Check(obj) ||
+#endif
+    PyLong_Check(obj) ||
+    PyFloat_Check(obj) ||
+    PyComplex_Check(obj)
+    );
+}
+
+/**
+ * Returns the type of object `op' is - a scalar (return value = 0), a
+ * blitz.array (return value = 1), a numpy.ndarray (return value = 2), an
+ * object which is convertible to a numpy.ndarray (return value = 3) or returns
+ * -1 if the object cannot be converted. No error is set on the python stack.
+ *
+ * If the object is convertible into a numpy.ndarray, then it is converted into
+ * a numpy ndarray and the resulting object is placed in `converted'. If
+ * `*converted' is set to 0 (NULL), then we don't try a conversion, returning
+ * -1.
+ */
+static int PyBobIoHDF5File_GetObjectType(PyObject* o, bob::io::HDF5Type& t,
+    PyObject** converted=0) {
+
+  if (PyArray_IsScalar(o, Generic) || PyBobIoHDF5File_IsPythonScalar(o)) {
+
+    if (PyArray_IsScalar(o, String))
+      return PyBobIoHDF5File_SetStringType(t, o);
+
+    else if (PyBool_Check(o))
+      return PyBobIoHDF5File_SetType<bool>(t);
+
+#if PY_VERSION_HEX < 0x03000000
+    else if (PyString_Check(o))
+      return PyBobIoHDF5File_SetStringType(t, o);
+
+#else
+    else if (PyBytes_Check(o))
+      return PyBobIoHDF5File_SetStringType(t, o);
+
+#endif
+    else if (PyUnicode_Check(o))
+      return PyBobIoHDF5File_SetStringType(t, o);
+
+#if PY_VERSION_HEX < 0x03000000
+    else if (PyInt_Check(o))
+      return PyBobIoHDF5File_SetType<int32_t>(t);
+
+#endif
+    else if (PyLong_Check(o))
+      return PyBobIoHDF5File_SetType<int64_t>(t);
+
+    else if (PyFloat_Check(o)) 
+      return PyBobIoHDF5File_SetType<double>(t);
+
+    else if (PyComplex_Check(o)) 
+      return PyBobIoHDF5File_SetType<std::complex<double> >(t);
+
+    else if (PyArray_IsScalar(o, Bool)) 
+      return PyBobIoHDF5File_SetType<bool>(t);
+
+    else if (PyArray_IsScalar(o, Int8)) 
+      return PyBobIoHDF5File_SetType<int8_t>(t);
+
+    else if (PyArray_IsScalar(o, UInt8)) 
+      return PyBobIoHDF5File_SetType<uint8_t>(t);
+
+    else if (PyArray_IsScalar(o, Int16)) 
+      return PyBobIoHDF5File_SetType<int16_t>(t);
+
+    else if (PyArray_IsScalar(o, UInt16)) 
+      return PyBobIoHDF5File_SetType<uint16_t>(t);
+
+    else if (PyArray_IsScalar(o, Int32)) 
+      return PyBobIoHDF5File_SetType<int32_t>(t);
+
+    else if (PyArray_IsScalar(o, UInt32))
+      return PyBobIoHDF5File_SetType<uint32_t>(t);
+
+    else if (PyArray_IsScalar(o, Int64)) 
+      return PyBobIoHDF5File_SetType<int64_t>(t);
+
+    else if (PyArray_IsScalar(o, UInt64)) 
+      return PyBobIoHDF5File_SetType<uint64_t>(t);
+
+    else if (PyArray_IsScalar(o, Float))
+      return PyBobIoHDF5File_SetType<float>(t);
+
+    else if (PyArray_IsScalar(o, Double)) 
+      return PyBobIoHDF5File_SetType<double>(t);
+
+    else if (PyArray_IsScalar(o, LongDouble)) 
+      return PyBobIoHDF5File_SetType<long double>(t);
+
+    else if (PyArray_IsScalar(o, CFloat)) 
+      return PyBobIoHDF5File_SetType<std::complex<float> >(t);
+
+    else if (PyArray_IsScalar(o, CDouble)) 
+      return PyBobIoHDF5File_SetType<std::complex<double> >(t);
+
+    else if (PyArray_IsScalar(o, CLongDouble)) 
+      return PyBobIoHDF5File_SetType<std::complex<long double> >(t);
+
+    //if you get to this, point, it is an unsupported scalar
+    return -1;
+
+  }
+
+  else if (PyBlitzArray_Check(o)) {
+
+    PyBlitzArrayObject* bz = reinterpret_cast<PyBlitzArrayObject*>(o);
+    bob::io::hdf5type h5type = PyBobIo_H5FromTypenum(bz->type_num);
+    if (h5type == bob::io::unsupported) return -1;
+    bob::io::HDF5Shape h5shape(bz->ndim, bz->shape);
+    t = bob::io::HDF5Type(h5type, h5shape);
+    return 1;
+
+  }
+
+  else if (PyArray_CheckExact(o) && PyArray_ISCARRAY_RO((PyArrayObject*)o)) {
+
+    PyArrayObject* np = reinterpret_cast<PyArrayObject*>(o);
+    bob::io::hdf5type h5type = PyBobIo_H5FromTypenum(PyArray_DESCR(np)->type_num);
+    if (h5type == bob::io::unsupported) return -1;
+    bob::io::HDF5Shape h5shape(PyArray_NDIM(np), PyArray_DIMS(np));
+    t = bob::io::HDF5Type(h5type, h5shape);
+    return 2;
+
+  }
+
+  else if (converted) {
+
+    *converted = PyArray_FromAny(o, 0, 1, 0, 
+#if     NPY_FEATURE_VERSION >= NUMPY17_API /* NumPy C-API version >= 1.7 */
+        NPY_ARRAY_CARRAY_RO, 
+#       else
+        NPY_CARRAY_RO, 
+#       endif
+        0);
+    if (!*converted) return -1; ///< error condition
+
+    PyArrayObject* np = reinterpret_cast<PyArrayObject*>(*converted);
+    bob::io::hdf5type h5type = PyBobIo_H5FromTypenum(PyArray_DESCR(np)->type_num);
+    if (h5type == bob::io::unsupported) return -1;
+    bob::io::HDF5Shape h5shape(PyArray_NDIM(np), PyArray_DIMS(np));
+    t = bob::io::HDF5Type(h5type, h5shape);
+    return 3;
+
+  }
+
+  //if you get to this, point, it is an unsupported type
+  return -1;
+
+}
+
+template <typename T>
+static PyObject* PyBobIoHDF5File_ReplaceScalar(PyBobIoHDF5FileObject* self,
+    const char* path, Py_ssize_t pos, PyObject* o) {
+
+  T value = PyBlitzArrayCxx_AsCScalar<T>(o);
+  if (PyErr_Occurred()) return 0;
+  self->f->replace(path, pos, value);
+
+  Py_RETURN_NONE;
+
+}
+
+static PyObject* PyBobIoHDF5File_Replace(PyBobIoHDF5FileObject* self, PyObject* args, PyObject* kwds) {
   
   /* Parses input arguments in a single shot */
   static const char* const_kwlist[] = {"path", "pos", "data", 0};
   static char** kwlist = const_cast<char**>(const_kwlist);
 
-  PyObject* key = 0;
+  char* path = 0;
   Py_ssize_t pos = -1;
   PyObject* data = 0;
-  if (!PyArg_ParseTupleAndKeywords(args, kwds, "OnO", kwlist, &key, &pos, &data)) return 0;
+  if (!PyArg_ParseTupleAndKeywords(args, kwds, "snO", kwlist, &path, &pos, &data)) return 0;
+
+  bob::io::HDF5Type type;
+  PyObject* converted = 0;
+  int is_array = PyBobIoHDF5File_GetObjectType(data, type, &converted);
+  if (is_array < 0) { ///< error condition, signal
+    PyErr_Format(PyExc_TypeError, "error replacing position %" PY_FORMAT_SIZE_T "d of dataset `%s' at HDF5 file `%s': no support for storing objects of type `%s' on HDF5 files", pos, path, self->f->filename().c_str(), data->ob_type->tp_name);
+    return 0;
+  }
+  
+  try {
+
+    if (!is_array) { //write as a scalar
+
+      switch(type.type()) {
+        case bob::io::s:
+          {
+            char* value = PyBobIo_GetString(data);
+            if (!value) return 0;
+            self->f->replace(path, pos, value);
+            Py_RETURN_NONE;
+          }
+        case bob::io::b:
+          return PyBobIoHDF5File_ReplaceScalar<bool>(self, path, pos, data);
+        case bob::io::i8:
+          return PyBobIoHDF5File_ReplaceScalar<int8_t>(self, path, pos, data);
+        case bob::io::i16:
+          return PyBobIoHDF5File_ReplaceScalar<int16_t>(self, path, pos, data);
+        case bob::io::i32:
+          return PyBobIoHDF5File_ReplaceScalar<int32_t>(self, path, pos, data);
+        case bob::io::i64:
+          return PyBobIoHDF5File_ReplaceScalar<int64_t>(self, path, pos, data);
+        case bob::io::u8:
+          return PyBobIoHDF5File_ReplaceScalar<uint8_t>(self, path, pos, data);
+        case bob::io::u16:
+          return PyBobIoHDF5File_ReplaceScalar<uint16_t>(self, path, pos, data);
+        case bob::io::u32:
+          return PyBobIoHDF5File_ReplaceScalar<uint32_t>(self, path, pos, data);
+        case bob::io::u64:
+          return PyBobIoHDF5File_ReplaceScalar<uint64_t>(self, path, pos, data);
+        case bob::io::f32:
+          return PyBobIoHDF5File_ReplaceScalar<float>(self, path, pos, data);
+        case bob::io::f64:
+          return PyBobIoHDF5File_ReplaceScalar<double>(self, path, pos, data);
+        case bob::io::f128:
+          return PyBobIoHDF5File_ReplaceScalar<long double>(self, path, pos, data);
+        case bob::io::c64:
+          return PyBobIoHDF5File_ReplaceScalar<std::complex<float> >(self, path, pos, data);
+        case bob::io::c128:
+          return PyBobIoHDF5File_ReplaceScalar<std::complex<double> >(self, path, pos, data);
+        case bob::io::c256:
+          return PyBobIoHDF5File_ReplaceScalar<std::complex<long double> >(self, path, pos, data);
+        default:
+          break;
+      }
+
+    }
+
+    else { //write as array
+
+      switch (is_array) {
+        case 1: //blitz.array
+          self->f->write_buffer(path, pos, type, ((PyBlitzArrayObject*)data)->data);
+          break;
+
+        case 2: //numpy.ndarray
+          self->f->write_buffer(path, pos, type, PyArray_DATA((PyArrayObject*)data));
+          break;
+
+        case 3: //converted numpy.ndarray
+          self->f->write_buffer(path, pos, type, PyArray_DATA((PyArrayObject*)converted));
+          Py_DECREF(converted);
+          break;
+
+        default:
+          PyErr_Format(PyExc_NotImplementedError, "error replacing position %" PY_FORMAT_SIZE_T "d of dataset `%s' at HDF5 file `%s': HDF5 replace function is uncovered for array type %d (DEBUG ME)", pos, path, self->f->filename().c_str(), is_array);
+          return 0;
+      }
+
+    }
+
+  }
+  catch (std::exception& e) {
+    PyErr_SetString(PyExc_RuntimeError, e.what());
+    return 0;
+  }
+  catch (...) {
+    PyErr_Format(PyExc_RuntimeError, "cannot replace object in position %" PY_FORMAT_SIZE_T "d at HDF5 file `%s': unknown exception caught", pos, self->f->filename().c_str());
+    return 0;
+  }
 
   Py_RETURN_NONE;
+
 }
 
 PyDoc_STRVAR(s_replace_str, "replace");
@@ -851,18 +1156,152 @@ data\n\
 \n\
 ");
 
-static PyObject* PyBobIoHDF5File_Append(PyBobIoHDF5FileObject* self, PyObject *args, PyObject* kwds) {
+template <typename T>
+static int PyBobIoHDF5File_AppendScalar(PyBobIoHDF5FileObject* self,
+    const char* path, PyObject* o) {
+
+  T value = PyBlitzArrayCxx_AsCScalar<T>(o);
+  if (PyErr_Occurred()) return 0;
+  self->f->append(path, value);
+
+  return 1;
+
+}
+
+static int PyBobIoHDF5File_InnerAppend(PyBobIoHDF5FileObject* self, const char* path, PyObject* data, Py_ssize_t compression) {
+
+  bob::io::HDF5Type type;
+  PyObject* converted = 0;
+  int is_array = PyBobIoHDF5File_GetObjectType(data, type, &converted);
+  if (is_array < 0) { ///< error condition, signal
+    PyErr_Format(PyExc_TypeError, "error appending to object `%s' of HDF5 file `%s': no support for storing objects of type `%s' on HDF5 files", path, self->f->filename().c_str(), data->ob_type->tp_name);
+    return 0;
+  }
   
+  try {
+
+    if (!is_array) { //write as a scalar
+
+      switch(type.type()) {
+        case bob::io::s:
+          {
+            char* value = PyBobIo_GetString(data);
+            if (!value) return 0;
+            self->f->append(path, value);
+            return 1;
+          }
+        case bob::io::b:
+          return PyBobIoHDF5File_AppendScalar<bool>(self, path, data);
+        case bob::io::i8:
+          return PyBobIoHDF5File_AppendScalar<int8_t>(self, path, data);
+        case bob::io::i16:
+          return PyBobIoHDF5File_AppendScalar<int16_t>(self, path, data);
+        case bob::io::i32:
+          return PyBobIoHDF5File_AppendScalar<int32_t>(self, path, data);
+        case bob::io::i64:
+          return PyBobIoHDF5File_AppendScalar<int64_t>(self, path, data);
+        case bob::io::u8:
+          return PyBobIoHDF5File_AppendScalar<uint8_t>(self, path, data);
+        case bob::io::u16:
+          return PyBobIoHDF5File_AppendScalar<uint16_t>(self, path, data);
+        case bob::io::u32:
+          return PyBobIoHDF5File_AppendScalar<uint32_t>(self, path, data);
+        case bob::io::u64:
+          return PyBobIoHDF5File_AppendScalar<uint64_t>(self, path, data);
+        case bob::io::f32:
+          return PyBobIoHDF5File_AppendScalar<float>(self, path, data);
+        case bob::io::f64:
+          return PyBobIoHDF5File_AppendScalar<double>(self, path, data);
+        case bob::io::f128:
+          return PyBobIoHDF5File_AppendScalar<long double>(self, path, data);
+        case bob::io::c64:
+          return PyBobIoHDF5File_AppendScalar<std::complex<float> >(self, path, data);
+        case bob::io::c128:
+          return PyBobIoHDF5File_AppendScalar<std::complex<double> >(self, path, data);
+        case bob::io::c256:
+          return PyBobIoHDF5File_AppendScalar<std::complex<long double> >(self, path, data);
+        default:
+          break;
+      }
+
+    }
+
+    else { //write as array
+
+      switch (is_array) {
+        case 1: //blitz.array
+          if (!self->f->contains(path)) self->f->create(path, type, false, compression);
+          self->f->extend_buffer(path, type, ((PyBlitzArrayObject*)data)->data);
+          break;
+
+        case 2: //numpy.ndarray
+          if (!self->f->contains(path)) self->f->create(path, type, false, compression);
+          self->f->extend_buffer(path, type, PyArray_DATA((PyArrayObject*)data));
+          break;
+
+        case 3: //converted numpy.ndarray
+          if (!self->f->contains(path)) self->f->create(path, type, false, compression);
+          self->f->extend_buffer(path, type, PyArray_DATA((PyArrayObject*)converted));
+          Py_DECREF(converted);
+          break;
+
+        default:
+          PyErr_Format(PyExc_NotImplementedError, "error appending to object `%s' at HDF5 file `%s': HDF5 replace function is uncovered for array type %d (DEBUG ME)", path, self->f->filename().c_str(), is_array);
+          return 0;
+      }
+
+    }
+
+  }
+  catch (std::exception& e) {
+    PyErr_SetString(PyExc_RuntimeError, e.what());
+    return 0;
+  }
+  catch (...) {
+    PyErr_Format(PyExc_RuntimeError, "cannot append to object `%s' at HDF5 file `%s': unknown exception caught", path, self->f->filename().c_str());
+    return 0;
+  }
+
+  return 1;
+
+}
+
+static PyObject* PyBobIoHDF5File_Append(PyBobIoHDF5FileObject* self, PyObject *args, PyObject* kwds) {
+ 
   /* Parses input arguments in a single shot */
   static const char* const_kwlist[] = {"path", "data", "compression", 0};
   static char** kwlist = const_cast<char**>(const_kwlist);
 
-  PyObject* key = 0;
+  char* path = 0;
   PyObject* data = 0;
   Py_ssize_t compression = 0;
-  if (!PyArg_ParseTupleAndKeywords(args, kwds, "OO|n", kwlist, &key, &data, &compression)) return 0;
+  if (!PyArg_ParseTupleAndKeywords(args, kwds, "sO|n", kwlist, &path, &data, &compression)) return 0;
 
+  if (compression < 0 || compression > 9) {
+    PyErr_SetString(PyExc_ValueError, "compression should be set to an integer value between and including 0 and 9");
+    return 0;
+  }
+
+  // special case: user passes a tuple or list of arrays or scalars to append
+  if (PyTuple_Check(data) || PyList_Check(data)) {
+    PyObject* iter = PyObject_GetIter(data);
+    if (!iter) return 0;
+    while (PyObject* item = PyIter_Next(iter)) {
+      int ok = PyBobIoHDF5File_InnerAppend(self, path, item, compression);
+      Py_DECREF(item);
+      if (!ok) {
+        Py_DECREF(iter);
+        return 0;
+      }
+    }
+    Py_DECREF(iter);
+    Py_RETURN_NONE;
+  }
+
+  int ok = PyBobIoHDF5File_InnerAppend(self, path, data, compression);
+  if (!ok) return 0;
   Py_RETURN_NONE;
+
 }
 
 PyDoc_STRVAR(s_append_str, "append");
@@ -895,18 +1334,128 @@ compression\n\
 \n\
 ");
 
-static PyObject* PyBobIoHDF5File_Set(PyBobIoHDF5FileObject* self, PyObject *args, PyObject* kwds) {
+template <typename T>
+static PyObject* PyBobIoHDF5File_SetScalar(PyBobIoHDF5FileObject* self,
+    const char* path, PyObject* o) {
+
+  T value = PyBlitzArrayCxx_AsCScalar<T>(o);
+  if (PyErr_Occurred()) return 0;
+  self->f->set(path, value);
+
+  Py_RETURN_NONE;
+
+}
+
+static PyObject* PyBobIoHDF5File_Set(PyBobIoHDF5FileObject* self, PyObject* args, PyObject* kwds) {
   
   /* Parses input arguments in a single shot */
   static const char* const_kwlist[] = {"path", "data", "compression", 0};
   static char** kwlist = const_cast<char**>(const_kwlist);
 
-  PyObject* key = 0;
+  char* path = 0;
   PyObject* data = 0;
   Py_ssize_t compression = 0;
-  if (!PyArg_ParseTupleAndKeywords(args, kwds, "OO|n", kwlist, &key, &data, &compression)) return 0;
+  if (!PyArg_ParseTupleAndKeywords(args, kwds, "sO|n", kwlist, &path, &data, &compression)) return 0;
+
+  if (compression < 0 || compression > 9) {
+    PyErr_SetString(PyExc_ValueError, "compression should be set to an integer value between and including 0 and 9");
+    return 0;
+  }
+
+  bob::io::HDF5Type type;
+  PyObject* converted = 0;
+  int is_array = PyBobIoHDF5File_GetObjectType(data, type, &converted);
+  if (is_array < 0) { ///< error condition, signal
+    PyErr_Format(PyExc_TypeError, "error setting object `%s' of HDF5 file `%s': no support for storing objects of type `%s' on HDF5 files", path, self->f->filename().c_str(), data->ob_type->tp_name);
+    return 0;
+  }
+  
+  try {
+
+    if (!is_array) { //write as a scalar
+
+      switch(type.type()) {
+        case bob::io::s:
+          {
+            char* value = PyBobIo_GetString(data);
+            if (!value) return 0;
+            self->f->set(path, value);
+            Py_RETURN_NONE;
+          }
+        case bob::io::b:
+          return PyBobIoHDF5File_SetScalar<bool>(self, path, data);
+        case bob::io::i8:
+          return PyBobIoHDF5File_SetScalar<int8_t>(self, path, data);
+        case bob::io::i16:
+          return PyBobIoHDF5File_SetScalar<int16_t>(self, path, data);
+        case bob::io::i32:
+          return PyBobIoHDF5File_SetScalar<int32_t>(self, path, data);
+        case bob::io::i64:
+          return PyBobIoHDF5File_SetScalar<int64_t>(self, path, data);
+        case bob::io::u8:
+          return PyBobIoHDF5File_SetScalar<uint8_t>(self, path, data);
+        case bob::io::u16:
+          return PyBobIoHDF5File_SetScalar<uint16_t>(self, path, data);
+        case bob::io::u32:
+          return PyBobIoHDF5File_SetScalar<uint32_t>(self, path, data);
+        case bob::io::u64:
+          return PyBobIoHDF5File_SetScalar<uint64_t>(self, path, data);
+        case bob::io::f32:
+          return PyBobIoHDF5File_SetScalar<float>(self, path, data);
+        case bob::io::f64:
+          return PyBobIoHDF5File_SetScalar<double>(self, path, data);
+        case bob::io::f128:
+          return PyBobIoHDF5File_SetScalar<long double>(self, path, data);
+        case bob::io::c64:
+          return PyBobIoHDF5File_SetScalar<std::complex<float> >(self, path, data);
+        case bob::io::c128:
+          return PyBobIoHDF5File_SetScalar<std::complex<double> >(self, path, data);
+        case bob::io::c256:
+          return PyBobIoHDF5File_SetScalar<std::complex<long double> >(self, path, data);
+        default:
+          break;
+      }
+
+    }
+
+    else { //write as array
+
+      switch (is_array) {
+        case 1: //blitz.array
+          if (!self->f->contains(path)) self->f->create(path, type, false, compression);
+          self->f->write_buffer(path, 0, type, ((PyBlitzArrayObject*)data)->data);
+          break;
+
+        case 2: //numpy.ndarray
+          if (!self->f->contains(path)) self->f->create(path, type, false, compression);
+          self->f->write_buffer(path, 0, type, PyArray_DATA((PyArrayObject*)data));
+          break;
+
+        case 3: //converted numpy.ndarray
+          if (!self->f->contains(path)) self->f->create(path, type, false, compression);
+          self->f->write_buffer(path, 0, type, PyArray_DATA((PyArrayObject*)converted));
+          Py_DECREF(converted);
+          break;
+
+        default:
+          PyErr_Format(PyExc_NotImplementedError, "error setting object `%s' at HDF5 file `%s': HDF5 replace function is uncovered for array type %d (DEBUG ME)", path, self->f->filename().c_str(), is_array);
+          return 0;
+      }
+
+    }
+
+  }
+  catch (std::exception& e) {
+    PyErr_SetString(PyExc_RuntimeError, e.what());
+    return 0;
+  }
+  catch (...) {
+    PyErr_Format(PyExc_RuntimeError, "cannot set object `%s' at HDF5 file `%s': unknown exception caught", path, self->f->filename().c_str());
+    return 0;
+  }
 
   Py_RETURN_NONE;
+
 }
 
 PyDoc_STRVAR(s_set_str, "set");
diff --git a/xbob/io/test/test_hdf5.py b/xbob/io/test/test_hdf5.py
index 42f56b8..30dce84 100644
--- a/xbob/io/test/test_hdf5.py
+++ b/xbob/io/test/test_hdf5.py
@@ -278,13 +278,13 @@ def test_can_load_hdf5_from_matlab():
   # interestingly enough, if you load those files as arrays, you will read
   # the whole data at once:
 
-  t = peek_all(testutils.datafile('matlab_1d.hdf5', __name__))
-  assert t.shape == (512,)
-  assert t.dtype == numpy.dtype('float64')
+  dtype, shape, stride = peek_all(testutils.datafile('matlab_1d.hdf5', __name__))
+  assert shape == (512,)
+  assert dtype == numpy.dtype('float64')
 
-  t = peek_all(testutils.datafile('matlab_2d.hdf5', __name__))
-  assert t.shape == (512, 2)
-  assert t.dtype == numpy.dtype('float64')
+  dtype, shape, stride = peek_all(testutils.datafile('matlab_2d.hdf5', __name__))
+  assert shape == (512, 2)
+  assert dtype == numpy.dtype('float64')
 
 def test_matlab_import():
 
-- 
GitLab