python: Wrap file-like objects on demand.
authorJustus Winter <justus@g10code.com>
Mon, 6 Jun 2016 11:11:15 +0000 (13:11 +0200)
committerJustus Winter <justus@g10code.com>
Mon, 6 Jun 2016 12:16:04 +0000 (14:16 +0200)
* lang/python/gpgme.i (gpgme_data_t): Use new function to create
wrapper objects if necessary, and deallocate them after the function
call.
* lang/python/helpers.c (object_to_gpgme_data_t): New function.
* lang/python/helpers.h (object_to_gpgme_data_t): New prototype.
* lang/python/tests/Makefile.am (pytests): Add new test.
* lang/python/tests/t-idiomatic.py: New file.

Signed-off-by: Justus Winter <justus@g10code.com>
lang/python/gpgme.i
lang/python/helpers.c
lang/python/helpers.h
lang/python/tests/Makefile.am
lang/python/tests/t-idiomatic.py [new file with mode: 0755]

index e3c761d..e369582 100644 (file)
 }
 
 // Special handling for references to our objects.
-%typemap(in) gpgme_data_t DATAIN {
+%typemap(in) gpgme_data_t DATAIN (PyObject *wrapper) {
+  /* If we create a temporary wrapper object, we will store it in
+     wrapperN, where N is $argnum.  Here in this fragment, SWIG will
+     automatically append $argnum.  */
+  wrapper = NULL;
   if ($input == Py_None)
     $1 = NULL;
   else {
     PyObject *pypointer = NULL;
 
-    if((pypointer=object_to_gpgme_t($input, "$1_ltype", $argnum)) == NULL)
+    if((pypointer=object_to_gpgme_data_t($input, $argnum, &wrapper)) == NULL)
       return NULL;
 
     /* input = $input, 1 = $1, 1_descriptor = $1_descriptor */
   }
 }
 
+%typemap(freearg) gpgme_data_t DATAIN {
+  /* Free the temporary wrapper, if any.  */
+  Py_XDECREF(wrapper$argnum);
+}
+
 %apply gpgme_data_t DATAIN {gpgme_data_t plain, gpgme_data_t cipher,
                        gpgme_data_t sig, gpgme_data_t signed_text,
                        gpgme_data_t plaintext, gpgme_data_t keydata,
index 3ecbacc..7e1c1c3 100644 (file)
@@ -180,6 +180,71 @@ object_to_gpgme_t(PyObject *input, const char *objtype, int argnum)
   return pypointer;
 }
 
+/* Convert object to a pointer to gpgme type, version for data
+   objects.  Constructs a wrapper Python on the fly e.g. for file-like
+   objects with a fileno method, returning it in WRAPPER.  This object
+   must be de-referenced when no longer needed.  */
+PyObject *
+object_to_gpgme_data_t(PyObject *input, int argnum, PyObject **wrapper)
+{
+  static PyObject *Data = NULL;
+  PyObject *data = input;
+  PyObject *fd;
+  PyObject *result;
+  *wrapper = NULL;
+
+  if (Data == NULL) {
+    PyObject *core;
+    PyObject *from_list = PyList_New(0);
+    core = PyImport_ImportModuleLevel("core", PyEval_GetGlobals(),
+                                      PyEval_GetLocals(), from_list, 1);
+    Py_XDECREF(from_list);
+    if (core) {
+      Data = PyDict_GetItemString(PyModule_GetDict(core), "Data");
+      Py_XINCREF(Data);
+    }
+    else
+      return NULL;
+  }
+
+  fd = PyObject_CallMethod(input, "fileno", NULL);
+  if (fd) {
+    /* File-like object with file number.  */
+    PyObject *args = NULL;
+    PyObject *kw = NULL;
+
+    /* We don't need the fd, as we have no constructor accepting file
+       descriptors directly.  */
+    Py_DECREF(fd);
+
+    args = PyTuple_New(0);
+    kw = PyDict_New();
+    if (args == NULL || kw == NULL)
+      {
+      fail:
+        Py_XDECREF(args);
+        Py_XDECREF(kw);
+        return NULL;
+      }
+
+    if (PyDict_SetItemString(kw, "file", input) < 0)
+      goto fail;
+
+    *wrapper = PyObject_Call(Data, args, kw);
+    if (*wrapper == NULL)
+      goto fail;
+
+    Py_DECREF(args);
+    Py_DECREF(kw);
+    data = *wrapper;
+  }
+  else
+    PyErr_Clear();
+
+  result = object_to_gpgme_t(data, "gpgme_data_t", argnum);
+  return result;
+}
+
 \f
 
 /* Callback support.  */
index 952b31f..2450263 100644 (file)
@@ -29,6 +29,8 @@ void pygpgme_exception_init(void);
 gpgme_error_t pygpgme_exception2code(void);
 
 PyObject *object_to_gpgme_t(PyObject *input, const char *objtype, int argnum);
+PyObject *object_to_gpgme_data_t(PyObject *input, int argnum,
+                                PyObject **wrapper);
 
 void pygpgme_clear_generic_cb(PyObject **cb);
 PyObject *pygpgme_raise_callback_exception(PyObject *self);
index 12db3a5..b51562c 100644 (file)
@@ -46,7 +46,8 @@ py_tests = t-wrapper.py \
        t-edit.py \
        t-wait.py \
        t-encrypt-large.py \
-       t-file-name.py
+       t-file-name.py \
+       t-idiomatic.py
 
 TESTS = $(top_srcdir)/tests/gpg/initial.test \
        $(py_tests) \
diff --git a/lang/python/tests/t-idiomatic.py b/lang/python/tests/t-idiomatic.py
new file mode 100755 (executable)
index 0000000..05a377e
--- /dev/null
@@ -0,0 +1,47 @@
+#!/usr/bin/env python3
+
+# Copyright (C) 2016 g10 Code GmbH
+#
+# This file is part of GPGME.
+#
+# GPGME 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.
+#
+# GPGME 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 Lesser General
+# Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public
+# License along with this program; if not, see <http://www.gnu.org/licenses/>.
+
+import os
+import tempfile
+from pyme import core, constants, errors
+import support
+
+support.init_gpgme(constants.PROTOCOL_OpenPGP)
+c = core.Context()
+
+# Demonstrate automatic wrapping of file-like objects with 'fileno'
+# method.
+with tempfile.TemporaryFile() as source, \
+     tempfile.TemporaryFile() as signed, \
+     tempfile.TemporaryFile() as sink:
+    source.write(b"Hallo Leute\n")
+    source.seek(0, os.SEEK_SET)
+
+    c.op_sign(source, signed, constants.SIG_MODE_NORMAL)
+    signed.seek(0, os.SEEK_SET)
+    c.op_verify(signed, None, sink)
+    result = c.op_verify_result()
+
+    assert len(result.signatures) == 1, "Unexpected number of signatures"
+    sig = result.signatures[0]
+    assert sig.summary == 0
+    assert errors.GPGMEError(sig.status).getcode() == errors.NO_ERROR
+
+    sink.seek(0, os.SEEK_SET)
+    assert sink.read() == b"Hallo Leute\n"