[git] GPGME - branch, master, updated. gpgme-1.6.0-178-g8997d88

by Justus Winter cvs at cvs.gnupg.org
Thu Jun 16 14:26:25 CEST 2016


This is an automated email from the git hooks/post-receive script. It was
generated because a ref change was pushed to the repository containing
the project "GnuPG Made Easy".

The branch, master has been updated
       via  8997d88bf97d1784706becbf8e9dc74e4656e311 (commit)
       via  a324d0cffe93cab955698c2c065b2f2227e379e4 (commit)
       via  856bcfe2934237011984fab0bc69800a7c25c34b (commit)
       via  6641c7814b30e3e8f18105b2636545cc1bd07552 (commit)
       via  f3618bc615e3eff1f52fb5849cbf0f0b95515a61 (commit)
       via  616929b6edf00b4a774b727385d39b785a112b90 (commit)
       via  5464060baef2da8f5ea377118758e451c55e3787 (commit)
       via  5492853d7b84b4e1d0b11b234e32252ba8d1608d (commit)
       via  7eef399d89d4c3877cb795ed5ba45ecb241e67be (commit)
       via  a852f99a0ac9dc7f7493b403f811f5f7518fae40 (commit)
       via  3bacce03e60dc45cc2da99a2f5c504612202e802 (commit)
      from  8173c4f1f8a145c4b1d454f6f05e26950e23d675 (commit)

Those revisions listed above that are new to this repository have
not appeared on any other notification email; so we list those
revisions in full, below.

- Log -----------------------------------------------------------------
commit 8997d88bf97d1784706becbf8e9dc74e4656e311
Author: Justus Winter <justus at g10code.com>
Date:   Tue Jun 14 13:28:37 2016 +0200

    python: Improve autmatically generated docstrings.
    
    * lang/python/gpgme.i: Add comment.
    * lang/python/pyme/core.py (__getattr__): Rewrite automatically
    generated doctrings for the wrapper methods.
    
    Signed-off-by: Justus Winter <justus at g10code.com>

diff --git a/lang/python/gpgme.i b/lang/python/gpgme.i
index c6ddbb4..8dbb0c2 100644
--- a/lang/python/gpgme.i
+++ b/lang/python/gpgme.i
@@ -21,11 +21,21 @@
 %include "cpointer.i"
 %include "cstring.i"
 
-// Generate doc strings for all methods.
+/* Generate doc strings for all methods.
+
+   This will generate docstrings of the form
+
+     gpgme_op_encrypt(ctx, recp, flags, plain, cipher) -> gpgme_error_t
+
+   which we transform into
+
+     ctx.op_encrypt(recp, flags, plain, cipher) -> gpgme_error_t
+
+   for automagically wrapped functions.  */
 %feature("autodoc", "0");
 
-/* Allow use of Unicode objects, bytes, and None for strings.  */
 
+/* Allow use of Unicode objects, bytes, and None for strings.  */
 %typemap(in) const char * {
   if ($input == Py_None)
     $1 = NULL;
diff --git a/lang/python/pyme/core.py b/lang/python/pyme/core.py
index c090331..09f71a1 100644
--- a/lang/python/pyme/core.py
+++ b/lang/python/pyme/core.py
@@ -24,6 +24,7 @@ and the 'Data' class describing buffers of data.
 
 """
 
+import re
 import weakref
 from . import pygpgme
 from .errors import errorcheck, GPGMEError
@@ -107,6 +108,7 @@ class GpgmeWrapper(object):
         else:
             return get(self)
 
+    _munge_docstring = re.compile(r'gpgme_([^(]*)\(([^,]*), (.*\) -> .*)')
     def __getattr__(self, key):
         """On-the-fly generation of wrapper methods and properties"""
         if key[0] == '_' or self._cprefix == None:
@@ -119,27 +121,28 @@ class GpgmeWrapper(object):
         func = getattr(pygpgme, name)
 
         if self._errorcheck(name):
-            def _funcwrap(slf, *args, **kwargs):
-                result = func(slf.wrapped, *args, **kwargs)
+            def _funcwrap(slf, *args):
+                result = func(slf.wrapped, *args)
                 if slf._callback_excinfo:
                     pygpgme.pygpgme_raise_callback_exception(slf)
                 return errorcheck(result, "Invocation of " + name)
         else:
-            def _funcwrap(slf, *args, **kwargs):
-                result = func(slf.wrapped, *args, **kwargs)
+            def _funcwrap(slf, *args):
+                result = func(slf.wrapped, *args)
                 if slf._callback_excinfo:
                     pygpgme.pygpgme_raise_callback_exception(slf)
                 return result
 
-        _funcwrap.__doc__ = getattr(func, "__doc__")
+        doc = self._munge_docstring.sub(r'\2.\1(\3', getattr(func, "__doc__"))
+        _funcwrap.__doc__ = doc
 
         # Monkey-patch the class.
         setattr(self.__class__, key, _funcwrap)
 
         # Bind the method to 'self'.
-        def wrapper(*args, **kwargs):
-            return _funcwrap(self, *args, **kwargs)
-        _funcwrap.__doc__ = getattr(func, "__doc__")
+        def wrapper(*args):
+            return _funcwrap(self, *args)
+        wrapper.__doc__ = doc
 
         return wrapper
 

commit a324d0cffe93cab955698c2c065b2f2227e379e4
Author: Justus Winter <justus at g10code.com>
Date:   Tue Jun 14 17:33:12 2016 +0200

    python: Make result objects more robust.
    
    Results returned by the GPGME are fragile, i.e. they are only valid
    until the next operation is performed in the context.
    
    We cannot arbitrarily constrain the lifetime of Python objects, we
    therefore create deep copies of the results.
    
    * lang/python/gpgme.i (gpgme_tofu_info_t): Turn these into a list.
    (gpgme_*_result_t): Create deep copies of these objects.
    * lang/python/helpers.c (pygpgme_wrap_fragile_result): New function.
    * lang/python/helpers.h (pygpgme_wrap_fragile_result): New prototype.
    * lang/python/pyme/results.py: New file.
    
    Signed-off-by: Justus Winter <justus at g10code.com>

diff --git a/lang/python/gpgme.i b/lang/python/gpgme.i
index 9cc2022..c6ddbb4 100644
--- a/lang/python/gpgme.i
+++ b/lang/python/gpgme.i
@@ -283,10 +283,11 @@
 
 // Make types containing 'next' field to be lists
 %ignore next;
-%typemap(out) gpgme_sig_notation_t, gpgme_engine_info_t, gpgme_subkey_t, gpgme_key_sig_t,
-	gpgme_user_id_t, gpgme_invalid_key_t, gpgme_recipient_t, gpgme_new_signature_t,
-	gpgme_signature_t, gpgme_import_status_t, gpgme_conf_arg_t, gpgme_conf_opt_t,
-	gpgme_conf_comp_t {
+%typemap(out) gpgme_sig_notation_t, gpgme_engine_info_t, gpgme_subkey_t,
+   gpgme_key_sig_t, gpgme_user_id_t, gpgme_invalid_key_t,
+   gpgme_recipient_t, gpgme_new_signature_t, gpgme_signature_t,
+   gpgme_import_status_t, gpgme_conf_arg_t, gpgme_conf_opt_t,
+   gpgme_conf_comp_t, gpgme_tofu_info_t {
   int i;
   int size = 0;
   $1_ltype curr;
@@ -300,6 +301,75 @@
   }
 }
 
+

+
+/* Wrap the fragile result objects into robust Python ones.  */
+%typemap(out) gpgme_encrypt_result_t {
+  PyObject *fragile;
+  fragile = SWIG_NewPointerObj(SWIG_as_voidptr($1), $1_descriptor,
+                               %newpointer_flags);
+  $result = pygpgme_wrap_fragile_result(fragile, "EncryptResult");
+  Py_DECREF(fragile);
+}
+
+%typemap(out) gpgme_decrypt_result_t {
+  PyObject *fragile;
+  fragile = SWIG_NewPointerObj(SWIG_as_voidptr($1), $1_descriptor,
+                               %newpointer_flags);
+  $result = pygpgme_wrap_fragile_result(fragile, "DecryptResult");
+  Py_DECREF(fragile);
+}
+
+%typemap(out) gpgme_sign_result_t {
+  PyObject *fragile;
+  fragile = SWIG_NewPointerObj(SWIG_as_voidptr($1), $1_descriptor,
+                               %newpointer_flags);
+  $result = pygpgme_wrap_fragile_result(fragile, "SignResult");
+  Py_DECREF(fragile);
+}
+
+%typemap(out) gpgme_verify_result_t {
+  PyObject *fragile;
+  fragile = SWIG_NewPointerObj(SWIG_as_voidptr($1), $1_descriptor,
+                               %newpointer_flags);
+  $result = pygpgme_wrap_fragile_result(fragile, "VerifyResult");
+  Py_DECREF(fragile);
+}
+
+%typemap(out) gpgme_import_result_t {
+  PyObject *fragile;
+  fragile = SWIG_NewPointerObj(SWIG_as_voidptr($1), $1_descriptor,
+                               %newpointer_flags);
+  $result = pygpgme_wrap_fragile_result(fragile, "ImportResult");
+  Py_DECREF(fragile);
+}
+
+%typemap(out) gpgme_genkey_result_t {
+  PyObject *fragile;
+  fragile = SWIG_NewPointerObj(SWIG_as_voidptr($1), $1_descriptor,
+                               %newpointer_flags);
+  $result = pygpgme_wrap_fragile_result(fragile, "GenkeyResult");
+  Py_DECREF(fragile);
+}
+
+%typemap(out) gpgme_keylist_result_t {
+  PyObject *fragile;
+  fragile = SWIG_NewPointerObj(SWIG_as_voidptr($1), $1_descriptor,
+                               %newpointer_flags);
+  $result = pygpgme_wrap_fragile_result(fragile, "KeylistResult");
+  Py_DECREF(fragile);
+}
+
+%typemap(out) gpgme_vfs_mount_result_t {
+  PyObject *fragile;
+  fragile = SWIG_NewPointerObj(SWIG_as_voidptr($1), $1_descriptor,
+                               %newpointer_flags);
+  $result = pygpgme_wrap_fragile_result(fragile, "VFSMountResult");
+  Py_DECREF(fragile);
+}
+
+

+
 // Include mapper for edit callbacks
 %typemap(in) (gpgme_edit_cb_t fnc, void *fnc_value) {
   if (! PyTuple_Check($input))
diff --git a/lang/python/helpers.c b/lang/python/helpers.c
index 6de2b8d..1b66146 100644
--- a/lang/python/helpers.c
+++ b/lang/python/helpers.c
@@ -272,6 +272,38 @@ object_to_gpgme_data_t(PyObject *input, int argnum, gpgme_data_t *wrapper,
 
 

 
+PyObject *
+pygpgme_wrap_fragile_result(PyObject *fragile, const char *classname)
+{
+  static PyObject *results;
+  PyObject *class;
+  PyObject *replacement;
+
+  if (results == NULL)
+    {
+      PyObject *from_list = PyList_New(0);
+      if (from_list == NULL)
+        return NULL;
+
+      results = PyImport_ImportModuleLevel("results", PyEval_GetGlobals(),
+                                           PyEval_GetLocals(), from_list, 1);
+      Py_DECREF(from_list);
+
+      if (results == NULL)
+        return NULL;
+    }
+
+  class = PyMapping_GetItemString(PyModule_GetDict(results), classname);
+  if (class == NULL)
+    return NULL;
+
+  replacement = PyObject_CallFunctionObjArgs(class, fragile, NULL);
+  Py_DECREF(class);
+  return replacement;
+}
+
+

+
 /* Callback support.  */
 static gpgme_error_t pyPassphraseCb(void *hook,
 				    const char *uid_hint,
diff --git a/lang/python/helpers.h b/lang/python/helpers.h
index 1564290..beb2682 100644
--- a/lang/python/helpers.h
+++ b/lang/python/helpers.h
@@ -34,6 +34,8 @@ PyObject *object_to_gpgme_data_t(PyObject *input, int argnum,
 				 gpgme_data_t *wrapper,
 				 PyObject **bytesio, Py_buffer *view);
 
+PyObject *pygpgme_wrap_fragile_result(PyObject *fragile, const char *classname);
+
 PyObject *pygpgme_raise_callback_exception(PyObject *self);
 
 PyObject *pygpgme_set_passphrase_cb(PyObject *self, PyObject *cb);
diff --git a/lang/python/pyme/results.py b/lang/python/pyme/results.py
new file mode 100644
index 0000000..e6e8968
--- /dev/null
+++ b/lang/python/pyme/results.py
@@ -0,0 +1,116 @@
+# Robust result objects
+#
+# 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 Lesser General Public License as
+# published by the Free Software Foundation; either version 2.1 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/>.
+
+"""Robust result objects
+
+Results returned by the underlying library are fragile, i.e. they are
+only valid until the next operation is performed in the context.
+
+We cannot arbitrarily constrain the lifetime of Python objects, we
+therefore create deep copies of the results.
+
+"""
+
+class Result(object):
+    """Result object
+
+    Describes the result of an operation.
+
+    """
+
+    """Convert to types"""
+    _type = {}
+
+    """Map functions over list attributes"""
+    _map = {}
+
+    """Automatically copy unless blacklisted"""
+    _blacklist = {
+        'acquire', 'append', 'disown', 'next', 'own', 'this', 'thisown',
+    }
+    def __init__(self, fragile):
+        for key, func in self._type.items():
+            setattr(self, key, func(getattr(fragile, key)))
+
+        for key, func in self._map.items():
+            setattr(self, key, list(map(func, getattr(fragile, key))))
+
+        for key, func in self._map.items():
+            setattr(self, key, list(map(func, getattr(fragile, key))))
+
+        for key in dir(fragile):
+            if key.startswith('_') or key in self._blacklist:
+                continue
+            if hasattr(self, key):
+                continue
+
+            setattr(self, key, getattr(fragile, key))
+
+    def __str__(self):
+        return '<{} {}>'.format(
+            self.__class__.__name__,
+            ', '.join('{}: {}'.format(k, getattr(self, k))
+                      for k in dir(self) if not k.startswith('_')))
+
+class InvalidKey(Result):
+    pass
+
+class EncryptResult(Result):
+    _map = dict(invalid_recipients=InvalidKey)
+
+class Recipient(Result):
+    pass
+
+class DecryptResult(Result):
+    _type = dict(wrong_key_usage=bool)
+    _map = dict(recipients=Recipient)
+
+class NewSignature(Result):
+    pass
+
+class SignResult(Result):
+    _map = dict(invalid_signers=InvalidKey, signatures=NewSignature)
+
+class Notation(Result):
+    pass
+
+class TofuInfo(Result):
+    pass
+
+class Signature(Result):
+    _type = dict(wrong_key_usage=bool, chain_model=bool)
+    _map = dict(notations=Notation, tofu=TofuInfo)
+
+class VerifyResult(Result):
+    _map = dict(signatures=Signature)
+
+class ImportStatus(Result):
+    pass
+
+class ImportResult(Result):
+    _map = dict(imports=ImportStatus)
+
+class GenkeyResult(Result):
+    _type = dict(primary=bool, sub=bool)
+
+class KeylistResult(Result):
+    _type = dict(truncated=bool)
+
+class VFSMountResult(Result):
+    pass

commit 856bcfe2934237011984fab0bc69800a7c25c34b
Author: Justus Winter <justus at g10code.com>
Date:   Tue Jun 14 13:48:33 2016 +0200

    python: Avoid creating SWIG proxy classes.
    
    * lang/python/Makefile.am (gpgme_wrap.c): Use '-builtin' to make SWIG
    generate builtin types for c types.
    * lang/python/gpgme.i (pygpgme_wrap_gpgme_data_t): Adapt slightly.
    
    Signed-off-by: Justus Winter <justus at g10code.com>

diff --git a/lang/python/Makefile.am b/lang/python/Makefile.am
index e156d46..8f0e74f 100644
--- a/lang/python/Makefile.am
+++ b/lang/python/Makefile.am
@@ -40,7 +40,7 @@ errors.i:
 	  `$(GPG_ERROR_CONFIG) --prefix`/include/gpg-error.h >$@
 
 gpgme_wrap.c pyme/pygpgme.py: gpgme.i errors.i gpgme.h copystamp
-	$(SWIG) -python -py3 $(SWIGOPT) \
+	$(SWIG) -python -py3 -builtin $(SWIGOPT) \
 	  -o $(builddir)/gpgme_wrap.c -outdir $(builddir)/pyme \
 	  $<
 
diff --git a/lang/python/gpgme.i b/lang/python/gpgme.i
index 65cd235..9cc2022 100644
--- a/lang/python/gpgme.i
+++ b/lang/python/gpgme.i
@@ -373,7 +373,7 @@ FILE *fdopen(int fildes, const char *mode);
 PyObject *
 pygpgme_wrap_gpgme_data_t(gpgme_data_t data)
 {
-  return SWIG_NewPointerObj(data, SWIGTYPE_p_gpgme_data, 0);
+  return SWIG_Python_NewPointerObj(NULL, data, SWIGTYPE_p_gpgme_data, 0);
 }
 
 gpgme_ctx_t

commit 6641c7814b30e3e8f18105b2636545cc1bd07552
Author: Justus Winter <justus at g10code.com>
Date:   Tue Jun 14 12:37:26 2016 +0200

    python: Simplify wrapping glue.
    
    * lang/python/pyme/core.py: Rename '_getctype' to '_ctype' and turn it
      into a string.  Likewise rename '_getnameprepend' to '_cprefix'.
    * lang/python/helpers.c: Adapt accordingly.
    
    Signed-off-by: Justus Winter <justus at g10code.com>

diff --git a/lang/python/helpers.c b/lang/python/helpers.c
index 5380ff2..6de2b8d 100644
--- a/lang/python/helpers.c
+++ b/lang/python/helpers.c
@@ -171,7 +171,7 @@ PyObject *
 object_to_gpgme_t(PyObject *input, const char *objtype, int argnum)
 {
   PyObject *pyname = NULL, *pypointer = NULL;
-  pyname = PyObject_CallMethod(input, "_getctype", NULL);
+  pyname = PyObject_GetAttrString(input, "_ctype");
   if (pyname && PyUnicode_Check(pyname))
     {
       if (strcmp(PyUnicode_AsUTF8(pyname), objtype) != 0)
@@ -261,7 +261,7 @@ object_to_gpgme_data_t(PyObject *input, int argnum, gpgme_data_t *wrapper,
     }
 
   /* As last resort we assume it is a wrapped data object.  */
-  if (PyObject_HasAttrString(data, "_getctype"))
+  if (PyObject_HasAttrString(data, "_ctype"))
     return object_to_gpgme_t(data, "gpgme_data_t", argnum);
 
   return PyErr_Format(PyExc_TypeError,
diff --git a/lang/python/pyme/core.py b/lang/python/pyme/core.py
index e5a5061..c090331 100644
--- a/lang/python/pyme/core.py
+++ b/lang/python/pyme/core.py
@@ -61,17 +61,22 @@ class GpgmeWrapper(object):
         else:
             return repr(self.wrapped) == repr(other.wrapped)
 
-    def _getctype(self):
-        """Must be implemented by child classes.
+    @property
+    def _ctype(self):
+        """The name of the c type wrapped by this class
+
+        Must be set by child classes.
 
-        Must return the name of the c type."""
+        """
         raise NotImplementedError()
 
-    def _getnameprepend(self):
-        """Must be implemented by child classes.
+    @property
+    def _cprefix(self):
+        """The common prefix of c functions wrapped by this class
+
+        Must be set by child classes.
 
-        Must return the prefix of all c functions mapped to methods of
-        this class."""
+        """
         raise NotImplementedError()
 
     def _errorcheck(self, name):
@@ -86,9 +91,9 @@ class GpgmeWrapper(object):
 
     def __wrap_boolean_property(self, key, do_set=False, value=None):
         get_func = getattr(pygpgme,
-                           "{}get_{}".format(self._getnameprepend(), key))
+                           "{}get_{}".format(self._cprefix, key))
         set_func = getattr(pygpgme,
-                           "{}set_{}".format(self._getnameprepend(), key))
+                           "{}set_{}".format(self._cprefix, key))
         def get(slf):
             return bool(get_func(slf.wrapped))
         def set_(slf, value):
@@ -104,13 +109,13 @@ class GpgmeWrapper(object):
 
     def __getattr__(self, key):
         """On-the-fly generation of wrapper methods and properties"""
-        if key[0] == '_' or self._getnameprepend() == None:
+        if key[0] == '_' or self._cprefix == None:
             return None
 
         if key in self._boolean_properties:
             return self.__wrap_boolean_property(key)
 
-        name = self._getnameprepend() + key
+        name = self._cprefix + key
         func = getattr(pygpgme, name)
 
         if self._errorcheck(name):
@@ -181,11 +186,8 @@ class Context(GpgmeWrapper):
     def pinentry_mode(self, value):
         self.set_pinentry_mode(value)
 
-    def _getctype(self):
-        return 'gpgme_ctx_t'
-
-    def _getnameprepend(self):
-        return 'gpgme_'
+    _ctype = 'gpgme_ctx_t'
+    _cprefix = 'gpgme_'
 
     def _errorcheck(self, name):
         """This function should list all functions returning gpgme_error_t"""
@@ -432,11 +434,8 @@ class Data(GpgmeWrapper):
 
     """
 
-    def _getctype(self):
-        return 'gpgme_data_t'
-
-    def _getnameprepend(self):
-        return 'gpgme_data_'
+    _ctype = 'gpgme_data_t'
+    _cprefix = 'gpgme_data_'
 
     def _errorcheck(self, name):
         """This function should list all functions returning gpgme_error_t"""

commit f3618bc615e3eff1f52fb5849cbf0f0b95515a61
Author: Justus Winter <justus at g10code.com>
Date:   Mon Jun 13 19:16:30 2016 +0200

    python: Rework callbacks.
    
    Simplify how the lifetime of callback arguments is managed.
    
    * lang/python/gpgme.i (gpgme_edit_cb_t): Check arguments.
    (PyObject_p_p, void_p_p): Drop rather dangerous interface.
    (pygpgme_unwrap_gpgme_ctx_t): New function.
    * lang/python/helpers.c (pygpgme_clear_generic_cb): Drop dangerous
    function.
    (pyPassphraseCb): Assert contract.
    (pygpgme_set_passphrase_cb): Use Python's calling convention so that
    we can raise exceptions.  Hand in 'self', get the wrapped object, and
    simply store the hook data as attribute of the wrapper object.
    (pyProgressCb, pygpgme_set_progress_cb): Likewise.
    (pygpgme_set_status_cb): Likewise.
    (pygpgme_data_new_from_cbs): Likewise.
    * lang/python/helpers.h (pygpgme_clear_generic_cb): Drop prototype.
    (pygpgme_set_passphrase_cb): Update prototype.
    (pygpgme_set_progress_cb): Likewise.
    (pygpgme_set_status_cb): Likewise.
    (pygpgme_data_new_from_cbs): Likewise.
    (pygpgme_unwrap_gpgme_ctx_t): New prottotype.
    * lang/python/pyme/core.py (Context, Data): Update callsites.
    
    Signed-off-by: Justus Winter <justus at g10code.com>

diff --git a/lang/python/gpgme.i b/lang/python/gpgme.i
index 1e4c9ff..65cd235 100644
--- a/lang/python/gpgme.i
+++ b/lang/python/gpgme.i
@@ -302,11 +302,14 @@
 
 // Include mapper for edit callbacks
 %typemap(in) (gpgme_edit_cb_t fnc, void *fnc_value) {
+  if (! PyTuple_Check($input))
+    return PyErr_Format(PyExc_TypeError, "edit callback must be a tuple");
+  if (PyTuple_Size($input) != 2 && PyTuple_Size($input) != 3)
+    return PyErr_Format(PyExc_TypeError,
+                        "edit callback must be a tuple of size 2 or 3");
+
   $1 = (gpgme_edit_cb_t) pyEditCb;
-  if ($input == Py_None)
-    $2 = NULL;
-  else
-    $2 = $input;
+  $2 = $input;
 }
 
 /* Include the unmodified <gpgme.h> for cc, and the cleaned-up local
@@ -355,8 +358,6 @@ struct _gpgme_sig_notation
 %pointer_functions(gpgme_error_t, gpgme_error_t_p);
 %pointer_functions(gpgme_trust_item_t, gpgme_trust_item_t_p);
 %pointer_functions(gpgme_engine_info_t, gpgme_engine_info_t_p);
-%pointer_functions(PyObject *, PyObject_p_p);
-%pointer_functions(void *, void_p_p);
 
 // Helper functions.
 
@@ -374,6 +375,18 @@ pygpgme_wrap_gpgme_data_t(gpgme_data_t data)
 {
   return SWIG_NewPointerObj(data, SWIGTYPE_p_gpgme_data, 0);
 }
+
+gpgme_ctx_t
+pygpgme_unwrap_gpgme_ctx_t(PyObject *wrapped)
+{
+  gpgme_ctx_t result;
+  if (SWIG_ConvertPtr(wrapped,
+                      (void **) &result,
+                      SWIGTYPE_p_gpgme_context,
+                      SWIG_POINTER_EXCEPTION) == -1)
+    return NULL;
+  return result;
+}
 %}
 
 %include "helpers.h"
diff --git a/lang/python/helpers.c b/lang/python/helpers.c
index ad33d07..5380ff2 100644
--- a/lang/python/helpers.c
+++ b/lang/python/helpers.c
@@ -76,10 +76,6 @@ gpgme_error_t pygpgme_exception2code(void) {
   return err_status;
 }
 
-void pygpgme_clear_generic_cb(PyObject **cb) {
-  Py_DECREF(*cb);
-}
-
 /* Exception support for callbacks.  */
 #define EXCINFO	"_callback_excinfo"
 
@@ -293,6 +289,7 @@ static gpgme_error_t pyPassphraseCb(void *hook,
   pygpgme_exception_init();
 
   assert (PyTuple_Check(pyhook));
+  assert (PyTuple_Size(pyhook) == 2 || PyTuple_Size(pyhook) == 3);
   self = PyTuple_GetItem(pyhook, 0);
   func = PyTuple_GetItem(pyhook, 1);
   if (PyTuple_Size(pyhook) == 3) {
@@ -374,15 +371,47 @@ static gpgme_error_t pyPassphraseCb(void *hook,
   return err_status;
 }
 
-void pygpgme_set_passphrase_cb(gpgme_ctx_t ctx, PyObject *cb,
-			       PyObject **freelater) {
+PyObject *
+pygpgme_set_passphrase_cb(PyObject *self, PyObject *cb) {
+  PyObject *wrapped;
+  gpgme_ctx_t ctx;
+
+  wrapped = PyObject_GetAttrString(self, "wrapped");
+  if (wrapped == NULL)
+    {
+      assert (PyErr_Occurred ());
+      return NULL;
+    }
+
+  ctx = pygpgme_unwrap_gpgme_ctx_t(wrapped);
+  Py_DECREF(wrapped);
+  if (ctx == NULL)
+    {
+      if (cb == Py_None)
+        goto out;
+      else
+        return PyErr_Format(PyExc_RuntimeError, "wrapped is NULL");
+    }
+
   if (cb == Py_None) {
     gpgme_set_passphrase_cb(ctx, NULL, NULL);
-    return;
+    PyObject_SetAttrString(self, "_passphrase_cb", Py_None);
+    goto out;
   }
-  Py_INCREF(cb);
-  *freelater = cb;
-  gpgme_set_passphrase_cb(ctx, (gpgme_passphrase_cb_t)pyPassphraseCb, (void *) cb);
+
+  if (! PyTuple_Check(cb))
+    return PyErr_Format(PyExc_TypeError, "cb must be a tuple");
+  if (PyTuple_Size(cb) != 2 && PyTuple_Size(cb) != 3)
+    return PyErr_Format(PyExc_TypeError,
+                        "cb must be a tuple of size 2 or 3");
+
+  gpgme_set_passphrase_cb(ctx, (gpgme_passphrase_cb_t) pyPassphraseCb,
+                          (void *) cb);
+  PyObject_SetAttrString(self, "_passphrase_cb", cb);
+
+ out:
+  Py_INCREF(Py_None);
+  return Py_None;
 }
 
 static void pyProgressCb(void *hook, const char *what, int type, int current,
@@ -392,6 +421,7 @@ static void pyProgressCb(void *hook, const char *what, int type, int current,
   PyObject *self = NULL;
 
   assert (PyTuple_Check(pyhook));
+  assert (PyTuple_Size(pyhook) == 2 || PyTuple_Size(pyhook) == 3);
   self = PyTuple_GetItem(pyhook, 0);
   func = PyTuple_GetItem(pyhook, 1);
   if (PyTuple_Size(pyhook) == 3) {
@@ -423,14 +453,46 @@ static void pyProgressCb(void *hook, const char *what, int type, int current,
   Py_XDECREF(retval);
 }
 
-void pygpgme_set_progress_cb(gpgme_ctx_t ctx, PyObject *cb, PyObject **freelater){
+PyObject *
+pygpgme_set_progress_cb(PyObject *self, PyObject *cb) {
+  PyObject *wrapped;
+  gpgme_ctx_t ctx;
+
+  wrapped = PyObject_GetAttrString(self, "wrapped");
+  if (wrapped == NULL)
+    {
+      assert (PyErr_Occurred ());
+      return NULL;
+    }
+
+  ctx = pygpgme_unwrap_gpgme_ctx_t(wrapped);
+  Py_DECREF(wrapped);
+  if (ctx == NULL)
+    {
+      if (cb == Py_None)
+        goto out;
+      else
+        return PyErr_Format(PyExc_RuntimeError, "wrapped is NULL");
+    }
+
   if (cb == Py_None) {
     gpgme_set_progress_cb(ctx, NULL, NULL);
-    return;
+    PyObject_SetAttrString(self, "_progress_cb", Py_None);
+    goto out;
   }
-  Py_INCREF(cb);
-  *freelater = cb;
+
+  if (! PyTuple_Check(cb))
+    return PyErr_Format(PyExc_TypeError, "cb must be a tuple");
+  if (PyTuple_Size(cb) != 2 && PyTuple_Size(cb) != 3)
+    return PyErr_Format(PyExc_TypeError,
+                        "cb must be a tuple of size 2 or 3");
+
   gpgme_set_progress_cb(ctx, (gpgme_progress_cb_t) pyProgressCb, (void *) cb);
+  PyObject_SetAttrString(self, "_progress_cb", cb);
+
+ out:
+  Py_INCREF(Py_None);
+  return Py_None;
 }
 

 /* Status callbacks.  */
@@ -488,15 +550,46 @@ static gpgme_error_t pyStatusCb(void *hook, const char *keyword,
   return err;
 }
 
-void pygpgme_set_status_cb(gpgme_ctx_t ctx, PyObject *cb,
-                           PyObject **freelater) {
+PyObject *
+pygpgme_set_status_cb(PyObject *self, PyObject *cb) {
+  PyObject *wrapped;
+  gpgme_ctx_t ctx;
+
+  wrapped = PyObject_GetAttrString(self, "wrapped");
+  if (wrapped == NULL)
+    {
+      assert (PyErr_Occurred ());
+      return NULL;
+    }
+
+  ctx = pygpgme_unwrap_gpgme_ctx_t(wrapped);
+  Py_DECREF(wrapped);
+  if (ctx == NULL)
+    {
+      if (cb == Py_None)
+        goto out;
+      else
+        return PyErr_Format(PyExc_RuntimeError, "wrapped is NULL");
+    }
+
   if (cb == Py_None) {
     gpgme_set_status_cb(ctx, NULL, NULL);
-    return;
+    PyObject_SetAttrString(self, "_status_cb", Py_None);
+    goto out;
   }
-  Py_INCREF(cb);
-  *freelater = cb;
+
+  if (! PyTuple_Check(cb))
+    return PyErr_Format(PyExc_TypeError, "cb must be a tuple");
+  if (PyTuple_Size(cb) != 2 && PyTuple_Size(cb) != 3)
+    return PyErr_Format(PyExc_TypeError,
+                        "cb must be a tuple of size 2 or 3");
+
   gpgme_set_status_cb(ctx, (gpgme_status_cb_t) pyStatusCb, (void *) cb);
+  PyObject_SetAttrString(self, "_status_cb", cb);
+
+ out:
+  Py_INCREF(Py_None);
+  return Py_None;
 }
 

 /* Edit callbacks.  */
@@ -775,9 +868,10 @@ static void pyDataReleaseCb(void *hook)
     pygpgme_stash_callback_exception(self);
 }
 
-gpgme_error_t pygpgme_data_new_from_cbs(gpgme_data_t *r_data,
-                                        PyObject *pycbs,
-                                        PyObject **freelater)
+PyObject *
+pygpgme_data_new_from_cbs(PyObject *self,
+                          PyObject *pycbs,
+                          gpgme_data_t *r_data)
 {
   static struct gpgme_data_cbs cbs = {
     pyDataReadCb,
@@ -785,12 +879,20 @@ gpgme_error_t pygpgme_data_new_from_cbs(gpgme_data_t *r_data,
     pyDataSeekCb,
     pyDataReleaseCb,
   };
+  gpgme_error_t err;
+
+  if (! PyTuple_Check(pycbs))
+    return PyErr_Format(PyExc_TypeError, "pycbs must be a tuple");
+  if (PyTuple_Size(pycbs) != 5 && PyTuple_Size(pycbs) != 6)
+    return PyErr_Format(PyExc_TypeError,
+                        "pycbs must be a tuple of size 5 or 6");
 
-  assert (PyTuple_Check(pycbs));
-  assert (PyTuple_Size(pycbs) == 5 || PyTuple_Size(pycbs) == 6);
+  err = gpgme_data_new_from_cbs(r_data, &cbs, (void *) pycbs);
+  if (err)
+    return pygpgme_raise_exception(err);
 
-  Py_INCREF(pycbs);
-  *freelater = pycbs;
+  PyObject_SetAttrString(self, "_data_cbs", pycbs);
 
-  return gpgme_data_new_from_cbs(r_data, &cbs, (void *) pycbs);
+  Py_INCREF(Py_None);
+  return Py_None;
 }
diff --git a/lang/python/helpers.h b/lang/python/helpers.h
index 37362ae..1564290 100644
--- a/lang/python/helpers.h
+++ b/lang/python/helpers.h
@@ -34,21 +34,18 @@ PyObject *object_to_gpgme_data_t(PyObject *input, int argnum,
 				 gpgme_data_t *wrapper,
 				 PyObject **bytesio, Py_buffer *view);
 
-void pygpgme_clear_generic_cb(PyObject **cb);
 PyObject *pygpgme_raise_callback_exception(PyObject *self);
 
-void pygpgme_set_passphrase_cb(gpgme_ctx_t ctx, PyObject *cb,
-			       PyObject **freelater);
-void pygpgme_set_progress_cb(gpgme_ctx_t ctx, PyObject *cb, PyObject **freelater);
-void pygpgme_set_status_cb(gpgme_ctx_t ctx, PyObject *cb,
-                           PyObject **freelater);
+PyObject *pygpgme_set_passphrase_cb(PyObject *self, PyObject *cb);
+PyObject *pygpgme_set_progress_cb(PyObject *self, PyObject *cb);
+PyObject *pygpgme_set_status_cb(PyObject *self, PyObject *cb);
 
 gpgme_error_t pyEditCb(void *opaque, gpgme_status_code_t status,
 		       const char *args, int fd);
 
-gpgme_error_t pygpgme_data_new_from_cbs(gpgme_data_t *r_data,
-                                        PyObject *pycbs,
-                                        PyObject **freelater);
+PyObject *pygpgme_data_new_from_cbs(PyObject *self, PyObject *pycbs,
+				    gpgme_data_t *r_data);
 
 /* SWIG support for helpers.c  */
 PyObject *pygpgme_wrap_gpgme_data_t(gpgme_data_t data);
+gpgme_ctx_t pygpgme_unwrap_gpgme_ctx_t(PyObject *wrapped);
diff --git a/lang/python/pyme/core.py b/lang/python/pyme/core.py
index 64dc787..e5a5061 100644
--- a/lang/python/pyme/core.py
+++ b/lang/python/pyme/core.py
@@ -220,9 +220,6 @@ class Context(GpgmeWrapper):
             pygpgme.delete_gpgme_ctx_t_p(tmp)
             self.own = True
         super().__init__(wrapped)
-        self.last_passcb = None
-        self.last_progresscb = None
-        self.last_statuscb = None
         self.armor = armor
         self.textmode = textmode
         self.offline = offline
@@ -247,30 +244,6 @@ class Context(GpgmeWrapper):
     def __exit__(self, type, value, tb):
         self.__del__()
 
-    def _free_passcb(self):
-        if self.last_passcb != None:
-            if pygpgme.pygpgme_clear_generic_cb:
-                pygpgme.pygpgme_clear_generic_cb(self.last_passcb)
-            if pygpgme.delete_PyObject_p_p:
-                pygpgme.delete_PyObject_p_p(self.last_passcb)
-            self.last_passcb = None
-
-    def _free_progresscb(self):
-        if self.last_progresscb != None:
-            if pygpgme.pygpgme_clear_generic_cb:
-                pygpgme.pygpgme_clear_generic_cb(self.last_progresscb)
-            if pygpgme.delete_PyObject_p_p:
-                pygpgme.delete_PyObject_p_p(self.last_progresscb)
-            self.last_progresscb = None
-
-    def _free_statuscb(self):
-        if self.last_statuscb != None:
-            if pygpgme.pygpgme_clear_generic_cb:
-                pygpgme.pygpgme_clear_generic_cb(self.last_statuscb)
-            if pygpgme.delete_PyObject_p_p:
-                pygpgme.delete_PyObject_p_p(self.last_statuscb)
-            self.last_statuscb = None
-
     def op_keylist_all(self, *args, **kwargs):
         self.op_keylist_start(*args, **kwargs)
         key = self.op_keylist_next()
@@ -341,16 +314,18 @@ class Context(GpgmeWrapper):
 
         Please see the GPGME manual for more information.
         """
-        self._free_passcb()
         if func == None:
             hookdata = None
         else:
-            self.last_passcb = pygpgme.new_PyObject_p_p()
             if hook == None:
                 hookdata = (weakref.ref(self), func)
             else:
                 hookdata = (weakref.ref(self), func, hook)
-        pygpgme.pygpgme_set_passphrase_cb(self.wrapped, hookdata, self.last_passcb)
+        pygpgme.pygpgme_set_passphrase_cb(self, hookdata)
+
+    def _free_passcb(self):
+        if pygpgme.pygpgme_set_passphrase_cb:
+            self.set_passphrase_cb(None)
 
     def set_progress_cb(self, func, hook=None):
         """Sets the progress meter callback to the function specified by FUNC.
@@ -364,16 +339,18 @@ class Context(GpgmeWrapper):
         Please see the GPGME manual for more information.
 
         """
-        self._free_progresscb()
         if func == None:
             hookdata = None
         else:
-            self.last_progresscb = pygpgme.new_PyObject_p_p()
             if hook == None:
                 hookdata = (weakref.ref(self), func)
             else:
                 hookdata = (weakref.ref(self), func, hook)
-        pygpgme.pygpgme_set_progress_cb(self.wrapped, hookdata, self.last_progresscb)
+        pygpgme.pygpgme_set_progress_cb(self, hookdata)
+
+    def _free_progresscb(self):
+        if pygpgme.pygpgme_set_progress_cb:
+            self.set_progress_cb(None)
 
     def set_status_cb(self, func, hook=None):
         """Sets the status callback to the function specified by FUNC.  If
@@ -386,17 +363,18 @@ class Context(GpgmeWrapper):
         Please see the GPGME manual for more information.
 
         """
-        self._free_statuscb()
         if func == None:
             hookdata = None
         else:
-            self.last_statuscb = pygpgme.new_PyObject_p_p()
             if hook == None:
                 hookdata = (weakref.ref(self), func)
             else:
                 hookdata = (weakref.ref(self), func, hook)
-        pygpgme.pygpgme_set_status_cb(self.wrapped, hookdata,
-                                      self.last_statuscb)
+        pygpgme.pygpgme_set_status_cb(self, hookdata)
+
+    def _free_statuscb(self):
+        if pygpgme.pygpgme_set_status_cb:
+            self.set_status_cb(None)
 
     def get_engine_info(self):
         """Returns this context specific engine info"""
@@ -547,12 +525,7 @@ class Data(GpgmeWrapper):
         self.__del__()
 
     def _free_datacbs(self):
-        if self.data_cbs != None:
-            if pygpgme.pygpgme_clear_generic_cb:
-                pygpgme.pygpgme_clear_generic_cb(self.data_cbs)
-            if pygpgme.delete_PyObject_p_p:
-                pygpgme.delete_PyObject_p_p(self.data_cbs)
-            self.data_cbs = None
+        self._data_cbs = None
 
     def new(self):
         tmp = pygpgme.new_gpgme_data_t_p()
@@ -579,8 +552,6 @@ class Data(GpgmeWrapper):
         pygpgme.delete_gpgme_data_t_p(tmp)
 
     def new_from_cbs(self, read_cb, write_cb, seek_cb, release_cb, hook=None):
-        assert self.data_cbs == None
-        self.data_cbs = pygpgme.new_PyObject_p_p()
         tmp = pygpgme.new_gpgme_data_t_p()
         if hook != None:
             hookdata = (weakref.ref(self),
@@ -588,8 +559,7 @@ class Data(GpgmeWrapper):
         else:
             hookdata = (weakref.ref(self),
                         read_cb, write_cb, seek_cb, release_cb)
-        errorcheck(
-            pygpgme.pygpgme_data_new_from_cbs(tmp, hookdata, self.data_cbs))
+        pygpgme.pygpgme_data_new_from_cbs(self, hookdata, tmp)
         self.wrapped = pygpgme.gpgme_data_t_p_value(tmp)
         pygpgme.delete_gpgme_data_t_p(tmp)
 

commit 616929b6edf00b4a774b727385d39b785a112b90
Author: Justus Winter <justus at g10code.com>
Date:   Wed Jun 8 17:56:33 2016 +0200

    python: Wrap objects implementing the buffer protocol.
    
    * lang/python/Makefile.am: Add the toplevel source directory to CFLAGS
    when compiling the bindings so that we can use private header files.
    * lang/python/gpgme.i (gpgme_data_t): Rework the object wrapping.  Do
    not create a Python wrapper object, merely a gpgme_data_t object, and
    keep references to buffer objects, if any.  If necessary, update the
    buffer after the function call.
    (pygpgme_wrap_gpgme_data_t): New function.
    * lang/python/helpers.c (object_to_gpgme_data_t): Rework object
    wrapping.  Also wrap objects implementing the buffer protocol.
    * lang/python/helpers.h (object_to_gpgme_data_t): Update prototype.
    (pygpgme_wrap_gpgme_data_t): New prototype.
    * lang/python/tests/t-idiomatic.py: Demonstrate this.
    
    Signed-off-by: Justus Winter <justus at g10code.com>

diff --git a/lang/python/Makefile.am b/lang/python/Makefile.am
index 18005bf..e156d46 100644
--- a/lang/python/Makefile.am
+++ b/lang/python/Makefile.am
@@ -45,7 +45,7 @@ gpgme_wrap.c pyme/pygpgme.py: gpgme.i errors.i gpgme.h copystamp
 	  $<
 
 all-local: gpgme_wrap.c pyme/pygpgme.py copystamp
-	CFLAGS="$(CFLAGS)" \
+	CFLAGS="$(CFLAGS) -I$(top_srcdir)" \
 	  $(PYTHON) setup.py build --verbose
 
 clean-local:
diff --git a/lang/python/gpgme.i b/lang/python/gpgme.i
index 98f30d5..1e4c9ff 100644
--- a/lang/python/gpgme.i
+++ b/lang/python/gpgme.i
@@ -114,17 +114,19 @@
 }
 
 // Special handling for references to our objects.
-%typemap(in) gpgme_data_t DATAIN (PyObject *wrapper) {
+%typemap(in) gpgme_data_t DATAIN (gpgme_data_t wrapper = NULL,
+                                  PyObject *bytesio = NULL, Py_buffer view) {
   /* 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;
+  memset(&view, 0, sizeof view);
   if ($input == Py_None)
     $1 = NULL;
   else {
-    PyObject *pypointer = NULL;
-
-    if((pypointer=object_to_gpgme_data_t($input, $argnum, &wrapper)) == NULL)
+    PyObject *pypointer;
+    pypointer = object_to_gpgme_data_t($input, $argnum, &wrapper,
+                                       &bytesio, &view);
+    if (pypointer == NULL)
       return NULL;
 
     /* input = $input, 1 = $1, 1_descriptor = $1_descriptor */
@@ -141,8 +143,79 @@
 }
 
 %typemap(freearg) gpgme_data_t DATAIN {
+  /* See whether we need to update the Python buffer.  */
+  if (resultobj && wrapper$argnum && view$argnum.buf
+      && wrapper$argnum->data.mem.buffer != NULL)
+    {
+      /* The buffer is dirty.  */
+      if (view$argnum.readonly)
+        {
+          Py_XDECREF(resultobj);
+          resultobj = NULL;
+          PyErr_SetString(PyExc_ValueError, "cannot update read-only buffer");
+        }
+
+      /* See if we need to truncate the buffer.  */
+      if (resultobj && view$argnum.len != wrapper$argnum->data.mem.length)
+        {
+          if (bytesio$argnum == NULL)
+            {
+              Py_XDECREF(resultobj);
+              resultobj = NULL;
+              PyErr_SetString(PyExc_ValueError, "cannot resize buffer");
+            }
+          else
+            {
+              PyObject *retval;
+              PyBuffer_Release(&view$argnum);
+              retval = PyObject_CallMethod(bytesio$argnum, "truncate", "l",
+                                           (long)
+                                           wrapper$argnum->data.mem.length);
+              if (retval == NULL)
+                {
+                  Py_XDECREF(resultobj);
+                  resultobj = NULL;
+                }
+              else
+                {
+                  Py_DECREF(retval);
+
+                  retval = PyObject_CallMethod(bytesio$argnum, "getbuffer", NULL);
+                  if (retval == NULL
+                      || PyObject_GetBuffer(retval, &view$argnum,
+                                            PyBUF_SIMPLE|PyBUF_WRITABLE) < 0)
+                    {
+                      Py_XDECREF(resultobj);
+                      resultobj = NULL;
+                    }
+
+                  Py_XDECREF(retval);
+
+                  if (resultobj && view$argnum.len
+                      != wrapper$argnum->data.mem.length)
+                    {
+                      Py_XDECREF(resultobj);
+                      resultobj = NULL;
+                      PyErr_Format(PyExc_ValueError,
+                                   "Expected buffer of length %zu, got %zi",
+                                   wrapper$argnum->data.mem.length,
+                                   view$argnum.len);
+                    }
+                }
+            }
+        }
+
+      if (resultobj)
+        memcpy(view$argnum.buf, wrapper$argnum->data.mem.buffer,
+               wrapper$argnum->data.mem.length);
+    }
+
   /* Free the temporary wrapper, if any.  */
-  Py_XDECREF(wrapper$argnum);
+  if (wrapper$argnum)
+    gpgme_data_release(wrapper$argnum);
+  Py_XDECREF (bytesio$argnum);
+  if (wrapper$argnum && view$argnum.buf)
+    PyBuffer_Release(&view$argnum);
 }
 
 %apply gpgme_data_t DATAIN {gpgme_data_t plain, gpgme_data_t cipher,
@@ -240,7 +313,10 @@
    version for SWIG.  We do, however, want to hide certain fields on
    some structs, which we provide prior to including the version for
    SWIG.  */
- %{ #include <gpgme.h> %}
+%{
+#include <gpgme.h>
+#include "src/data.h"	/* For struct gpgme_data.  */
+%}
 
 /* This is for notations, where we want to hide the length fields, and
    the unused bit field block.  */
@@ -291,5 +367,13 @@ FILE *fdopen(int fildes, const char *mode);
 
 %{
 #include "helpers.h"
+
+/* SWIG support for helpers.c  */
+PyObject *
+pygpgme_wrap_gpgme_data_t(gpgme_data_t data)
+{
+  return SWIG_NewPointerObj(data, SWIGTYPE_p_gpgme_data, 0);
+}
 %}
+
 %include "helpers.h"
diff --git a/lang/python/helpers.c b/lang/python/helpers.c
index 810423d..ad33d07 100644
--- a/lang/python/helpers.c
+++ b/lang/python/helpers.c
@@ -206,64 +206,72 @@ object_to_gpgme_t(PyObject *input, const char *objtype, int argnum)
    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)
+object_to_gpgme_data_t(PyObject *input, int argnum, gpgme_data_t *wrapper,
+                       PyObject **bytesio, Py_buffer *view)
 {
-  static PyObject *Data = NULL;
-  PyObject *data = input;
+  gpgme_error_t err;
+  PyObject *data;
   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;
-  }
 
+  /* See if it is a file-like object with file number.  */
   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.  */
+    err = gpgme_data_new_from_fd(wrapper, (int) PyLong_AsLong(fd));
     Py_DECREF(fd);
+    if (err)
+      return pygpgme_raise_exception (err);
+
+    return pygpgme_wrap_gpgme_data_t(*wrapper);
+  }
+  else
+    PyErr_Clear();
 
-    args = PyTuple_New(0);
-    kw = PyDict_New();
-    if (args == NULL || kw == NULL)
-      {
-      fail:
-        Py_XDECREF(args);
-        Py_XDECREF(kw);
+  /* No?  Maybe it implements the buffer protocol.  */
+  data = PyObject_CallMethod(input, "getbuffer", NULL);
+  if (data)
+    {
+      /* Save a reference to input, which seems to be a BytesIO
+         object.  */
+      Py_INCREF(input);
+      *bytesio = input;
+    }
+  else
+    {
+      PyErr_Clear();
+
+      /* No, but maybe the user supplied a buffer object?  */
+      data = input;
+    }
+
+  /* Do we have a buffer object?  */
+  if (PyObject_CheckBuffer(data))
+    {
+      if (PyObject_GetBuffer(data, view, PyBUF_SIMPLE) < 0)
         return NULL;
-      }
 
-    if (PyDict_SetItemString(kw, "file", input) < 0)
-      goto fail;
+      if (data != input)
+        Py_DECREF(data);
 
-    *wrapper = PyObject_Call(Data, args, kw);
-    if (*wrapper == NULL)
-      goto fail;
+      assert (view->ndim == 1);
+      assert (view->shape == NULL);
+      assert (view->strides == NULL);
+      assert (view->suboffsets == NULL);
 
-    Py_DECREF(args);
-    Py_DECREF(kw);
-    data = *wrapper;
-  }
-  else
-    PyErr_Clear();
+      err = gpgme_data_new_from_mem(wrapper, view->buf, (size_t) view->len, 0);
+      if (err)
+        return pygpgme_raise_exception (err);
 
-  result = object_to_gpgme_t(data, "gpgme_data_t", argnum);
-  return result;
+      return pygpgme_wrap_gpgme_data_t(*wrapper);
+    }
+
+  /* As last resort we assume it is a wrapped data object.  */
+  if (PyObject_HasAttrString(data, "_getctype"))
+    return object_to_gpgme_t(data, "gpgme_data_t", argnum);
+
+  return PyErr_Format(PyExc_TypeError,
+                      "arg %d: expected pyme.Data, file, or an object "
+                      "implementing the buffer protocol, got %s",
+                      argnum, data->ob_type->tp_name);
 }
 
 

diff --git a/lang/python/helpers.h b/lang/python/helpers.h
index 2450263..37362ae 100644
--- a/lang/python/helpers.h
+++ b/lang/python/helpers.h
@@ -1,4 +1,5 @@
 /*
+# Copyright (C) 2016 g10 Code GmbH
 # Copyright (C) 2004 Igor Belyi <belyi at users.sourceforge.net>
 # Copyright (C) 2002 John Goerzen <jgoerzen at complete.org>
 #
@@ -30,7 +31,8 @@ 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);
+				 gpgme_data_t *wrapper,
+				 PyObject **bytesio, Py_buffer *view);
 
 void pygpgme_clear_generic_cb(PyObject **cb);
 PyObject *pygpgme_raise_callback_exception(PyObject *self);
@@ -47,3 +49,6 @@ gpgme_error_t pyEditCb(void *opaque, gpgme_status_code_t status,
 gpgme_error_t pygpgme_data_new_from_cbs(gpgme_data_t *r_data,
                                         PyObject *pycbs,
                                         PyObject **freelater);
+
+/* SWIG support for helpers.c  */
+PyObject *pygpgme_wrap_gpgme_data_t(gpgme_data_t data);
diff --git a/lang/python/tests/t-idiomatic.py b/lang/python/tests/t-idiomatic.py
index 37cfb64..b252690 100755
--- a/lang/python/tests/t-idiomatic.py
+++ b/lang/python/tests/t-idiomatic.py
@@ -17,6 +17,7 @@
 # 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 io
 import os
 import tempfile
 from pyme import core, constants, errors
@@ -33,14 +34,7 @@ with core.Context() as c, core.Data() as d:
 assert leak_c.wrapped == None
 assert leak_d.wrapped == None
 
-# 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)
-
+def sign_and_verify(source, signed, sink):
     with core.Context() as c:
         c.op_sign(source, signed, constants.SIG_MODE_NORMAL)
         signed.seek(0, os.SEEK_SET)
@@ -54,3 +48,28 @@ with tempfile.TemporaryFile() as source, \
 
     sink.seek(0, os.SEEK_SET)
     assert sink.read() == b"Hallo Leute\n"
+
+# 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)
+
+    sign_and_verify(source, signed, sink)
+
+# XXX: Python's io.BytesIo.truncate does not work as advertised.
+# http://bugs.python.org/issue27261
+bio = io.BytesIO()
+bio.truncate(1)
+if len(bio.getvalue()) != 1:
+    # This version of Python is affected, preallocate buffer.
+    preallocate = 128*b'\x00'
+else:
+    preallocate = b''
+
+# Demonstrate automatic wrapping of objects implementing the buffer
+# interface, and the use of data objects with the 'with' statement.
+with io.BytesIO(preallocate) as signed, core.Data() as sink:
+    sign_and_verify(b"Hallo Leute\n", signed, sink)

commit 5464060baef2da8f5ea377118758e451c55e3787
Author: Justus Winter <justus at g10code.com>
Date:   Fri Jun 10 13:00:33 2016 +0200

    python: Add properties to wrapped object.
    
    * lang/python/pyme/core.py (GpgmeWrapper.__repr__): Saner
    representation.
    (GpgmeWrapper.__str__): Construct a nicer human readable string.
    (GpgmeWrapper._boolean_properties): New field.
    (GpgmeWrapper.__wrap_boolean_property): New function.
    (GpgmeWrapper.__getattr__): Wrap functions using properties.
    (GpgmeWrapper.__setattr__): New method.  Likewise wrap functions.
    (Context.signers): New property.
    (Context.pinentry_mode): Likewise.
    (Context._boolean_properties): List boolean properties.
    (Context.__init__): Add keyword arguments for properties and apply
    them.
    
    Signed-off-by: Justus Winter <justus at g10code.com>

diff --git a/lang/python/pyme/core.py b/lang/python/pyme/core.py
index 293df09..64dc787 100644
--- a/lang/python/pyme/core.py
+++ b/lang/python/pyme/core.py
@@ -27,6 +27,7 @@ and the 'Data' class describing buffers of data.
 import weakref
 from . import pygpgme
 from .errors import errorcheck, GPGMEError
+from . import constants
 from . import errors
 
 class GpgmeWrapper(object):
@@ -41,12 +42,15 @@ class GpgmeWrapper(object):
         self.wrapped = wrapped
 
     def __repr__(self):
-        return '<instance of %s.%s with GPG object at %s>' % \
-               (__name__, self.__class__.__name__,
-                self.wrapped)
+        return '<{}/{!r}>'.format(super().__repr__(), self.wrapped)
 
     def __str__(self):
-        return repr(self)
+        acc = ['{}.{}'.format(__name__, self.__class__.__name__)]
+        flags = [f for f in self._boolean_properties if getattr(self, f)]
+        if flags:
+            acc.append('({})'.format(' '.join(flags)))
+
+        return '<{}>'.format(' '.join(acc))
 
     def __hash__(self):
         return hash(repr(self.wrapped))
@@ -77,10 +81,35 @@ class GpgmeWrapper(object):
         returning gpgme_error_t."""
         raise NotImplementedError()
 
+    """The set of all boolean properties"""
+    _boolean_properties = set()
+
+    def __wrap_boolean_property(self, key, do_set=False, value=None):
+        get_func = getattr(pygpgme,
+                           "{}get_{}".format(self._getnameprepend(), key))
+        set_func = getattr(pygpgme,
+                           "{}set_{}".format(self._getnameprepend(), key))
+        def get(slf):
+            return bool(get_func(slf.wrapped))
+        def set_(slf, value):
+            set_func(slf.wrapped, bool(value))
+
+        p = property(get, set_, doc="{} flag".format(key))
+        setattr(self.__class__, key, p)
+
+        if do_set:
+            set_(self, bool(value))
+        else:
+            return get(self)
+
     def __getattr__(self, key):
-        """On-the-fly generation of wrapper methods."""
+        """On-the-fly generation of wrapper methods and properties"""
         if key[0] == '_' or self._getnameprepend() == None:
             return None
+
+        if key in self._boolean_properties:
+            return self.__wrap_boolean_property(key)
+
         name = self._getnameprepend() + key
         func = getattr(pygpgme, name)
 
@@ -109,6 +138,13 @@ class GpgmeWrapper(object):
 
         return wrapper
 
+    def __setattr__(self, key, value):
+        """On-the-fly generation of properties"""
+        if key in self._boolean_properties:
+            self.__wrap_boolean_property(key, True, value)
+        else:
+            super().__setattr__(key, value)
+
 class Context(GpgmeWrapper):
     """Context for cryptographic operations
 
@@ -122,6 +158,29 @@ class Context(GpgmeWrapper):
 
     """
 
+    @property
+    def signers(self):
+        """Keys used for signing"""
+        return [self.signers_enum(i) for i in range(self.signers_count())]
+    @signers.setter
+    def signers(self, signers):
+        old = self.signers
+        self.signers_clear()
+        try:
+            for key in signers:
+                self.signers_add(key)
+        except:
+            self.signers = old
+            raise
+
+    @property
+    def pinentry_mode(self):
+        """Pinentry mode"""
+        return self.get_pinentry_mode()
+    @pinentry_mode.setter
+    def pinentry_mode(self, value):
+        self.set_pinentry_mode(value)
+
     def _getctype(self):
         return 'gpgme_ctx_t'
 
@@ -139,7 +198,19 @@ class Context(GpgmeWrapper):
             return 1
         return 0
 
-    def __init__(self, wrapped=None):
+    _boolean_properties = {'armor', 'textmode', 'offline'}
+    def __init__(self, armor=False, textmode=False, offline=False,
+                 signers=[], pinentry_mode=constants.PINENTRY_MODE_DEFAULT,
+                 wrapped=None):
+        """Construct a context object
+
+        Keyword arguments:
+        armor		-- enable ASCII armoring (default False)
+        textmode	-- enable canonical text mode (default False)
+        offline		-- do not contact external key sources (default False)
+        signers		-- list of keys used for signing (default [])
+        pinentry_mode	-- pinentry mode (default PINENTRY_MODE_DEFAULT)
+        """
         if wrapped:
             self.own = False
         else:
@@ -152,6 +223,11 @@ class Context(GpgmeWrapper):
         self.last_passcb = None
         self.last_progresscb = None
         self.last_statuscb = None
+        self.armor = armor
+        self.textmode = textmode
+        self.offline = offline
+        self.signers = signers
+        self.pinentry_mode = pinentry_mode
 
     def __del__(self):
         if not pygpgme:

commit 5492853d7b84b4e1d0b11b234e32252ba8d1608d
Author: Justus Winter <justus at g10code.com>
Date:   Mon Jun 6 14:08:59 2016 +0200

    python: Improve the documentation.
    
    * lang/python/Makefile.am: Copy the README file.
    * lang/python/README: Rename, convert to org, and update.
    * lang/python/pyme/__init__.py: Move license out of the docstring,
    update docstring.
    * lang/python/pyme/core.py: Add and update docstrings.
    
    Signed-off-by: Justus Winter <justus at g10code.com>

diff --git a/lang/python/Makefile.am b/lang/python/Makefile.am
index 2c84f2b..18005bf 100644
--- a/lang/python/Makefile.am
+++ b/lang/python/Makefile.am
@@ -16,16 +16,21 @@
 # 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/>.
 
-EXTRA_DIST = README.rst
+EXTRA_DIST = README
 SUBDIRS = tests
 
+COPY_FILES = \
+	$(srcdir)/README \
+	$(srcdir)/pyme \
+	$(srcdir)/helpers.c $(srcdir)/helpers.h
+
 # Cleanup gpgme.h from deprecated functions and typedefs.
 gpgme.h: ../../src/gpgme.h $(srcdir)/gpgme-h-clean.py
 	$(PYTHON) $(srcdir)/gpgme-h-clean.py $< >$@
 
 # For VPATH builds we need to copy some files because Python's
 # distutils are not VPATH-aware.
-copystamp: $(srcdir)/pyme $(srcdir)/helpers.c $(srcdir)/helpers.h
+copystamp: $(COPY_FILES)
 	if test "$(srcdir)" != "$(builddir)" ; then cp -r $^ . ; fi
 	touch $@
 
diff --git a/lang/python/README b/lang/python/README
new file mode 100644
index 0000000..7ce8894
--- /dev/null
+++ b/lang/python/README
@@ -0,0 +1,57 @@
+PyME - GPGME for Python          emacs, please switch to -*- org -*- mode
+=======================
+
+PyMe is a python interface to the GPGME library:
+https://www.gnupg.org/related_software/gpgme/
+
+PyMe uses SWIG to create wrapper functions with automatic type
+conversions.  This way most of the functions and types are converted
+from C into Python 3 automatically by SWIG, reducing the maintenance
+cost of the binginds.
+
+* Authors
+
+PyME has been created by John Goerzen, and maintained, developed, and
+cherished by Igor Belyi, Martin Albrecht, Ben McGinnes, and everyone
+who contributed to it in any way.
+
+In 2016 we merged a port of PyME to into the GPGME repository, and
+development will continue there.  Please see the VCS history for the
+list of contributors, and if you do find bugs, or want to contribute,
+please get in touch and help maintain PyME.
+
+Please see the section 'History' further down this document for
+references to previous versions.
+
+* Mailing List
+
+For general discussion and help see the gnupg-users mailing list:
+https://lists.gnupg.org/mailman/listinfo/gnupg-users
+
+For development see the gnupg-devel mailing list:
+https://lists.gnupg.org/mailman/listinfo/gnupg-devel
+
+* Bugs
+
+Please report bugs using our bug tracker using the category 'gpgme',
+and topic 'python':
+https://bugs.gnupg.org/gnupg/
+
+* History
+
+ - The bindings have been merged into the GPGME repository in 2016.
+
+ - The latest version of PyME for Python 3.2 and above (as of
+   May, 2015) is v0.9.1.
+   https://git.gnupg.org/gpgme.git/lang/py3-pyme
+
+ - The latest version of PyME for Python 2.6 and 2.7 (as of this
+   writing) is v0.9.0.  https://bitbucket.org/malb/pyme
+
+ - A previous version of PyME v0.8.0 can be found on sourceforge:
+   http://pyme.sourceforge.net/
+
+ - A previous version of PyME v0.5.1 which works with GPGME v0.3.15
+   can be found on John Goerzen's PyMe page:
+   http://quux.org/devel/pyme/
+   http://www.complete.org/JohnGoerzen
diff --git a/lang/python/README.rst b/lang/python/README.rst
deleted file mode 100644
index 57df1f2..0000000
--- a/lang/python/README.rst
+++ /dev/null
@@ -1,48 +0,0 @@
-====
-PyME
-====
-
-PyMe is a python interface to the `GPGME
-<https://www.gnupg.org/related_software/gpgme/>`_ library.
-
-PyMe's development model is a `GPGME
-<https://www.gnupg.org/related_software/gpgme/>`_ + Python 3 + `SWIG
-<http://www.swig.org/>`_ combination which means that most of the functions and
-types are converted from C into Python 3 automatically by SWIG. In short, to be
-able to use PyMe you need to be `familiar with GPGME
-<https://www.gnupg.org/documentation/manuals/gpgme/>`_.
-
-
--------
-Authors
--------
-
-* John Goerzen, `Complete.Org <http://www.complete.org/JohnGoerzen>`_, 2002.
-* Igor Belyi, `PyME 0.8 <http://pyme.sourceforge.net/>`_, 2004-2008.
-* Martin Albrecht, `PyME 0.9+ <https://bitbucket.org/malb/pyme>`_, 2014 to present.
-* Ben McGinnes, `PyME Python 3 Port <https://git.gnupg.org/gpgme.git/lang/py3-pyme>`_, 2015 to present.
-
-
-------------
-Mailing List
-------------
-
-PyME's support and development `mailing list
-<http://sourceforge.net/p/pyme/mailman/pyme-help/>`_ is hosted by sourceforge.
-
-
--------
-History
--------
-
-* The latest version of PyME for Python 3.2 and above (as of May, 2015) is v0.9.1.
-  https://git.gnupg.org/gpgme.git/lang/py3-pyme
-
-* The latest version of PyME for Python 2.6 and 2.7 (as of this writing) is v0.9.0.
-  https://bitbucket.org/malb/pyme
-
-* A previous version of PyME v0.8.0 can be found on sourceforge:
-  http://pyme.sourceforge.net/
-
-* A previous version of PyME v0.5.1 which works with GPGME v0.3.15 can be found
-  on John Goerzen's PyMe page: http://quux.org/devel/pyme/
diff --git a/lang/python/pyme/__init__.py b/lang/python/pyme/__init__.py
index d06866a..e377f59 100644
--- a/lang/python/pyme/__init__.py
+++ b/lang/python/pyme/__init__.py
@@ -1,36 +1,38 @@
-"""
-Pyme: GPGME Interface for Python
-Copyright (C) 2004 Igor Belyi <belyi at users.sourceforge.net>
-Copyright (C) 2002 John Goerzen <jgoerzen at complete.org>
-
-   This library is free software; you can redistribute it and/or
-   modify it under the terms of the GNU Lesser General Public
-   License as published by the Free Software Foundation; either
-   version 2.1 of the License, or (at your option) any later version.
-
-   This library 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 library; if not, write to the Free Software
-   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307  USA
+# Copyright (C) 2016 g10 Code GmbH
+# Copyright (C) 2004 Igor Belyi <belyi at users.sourceforge.net>
+# Copyright (C) 2002 John Goerzen <jgoerzen at complete.org>
+#
+# This library is free software; you can redistribute it and/or
+# modify it under the terms of the GNU Lesser General Public
+# License as published by the Free Software Foundation; either
+# version 2.1 of the License, or (at your option) any later version.
+#
+# This library 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 library; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307  USA
+
+"""Pyme: GPGME Interface for Python
 
 Welcome to PyME, the GPGME Interface for Python.  "Pyme", when prounced,
 rhymes with "Pine".
 
 The latest release of this package may be obtained from
+https://www.gnupg.org
+
+Previous releases of this package for Python 2 can be obtained from
 http://pyme.sourceforge.net
-Previous releases of this package can be obtained from
-http://quux.org/devel/pyme/
 
 FEATURES
 --------
 
  * Feature-rich, full implementation of the GPGME library.  Supports
-   all GPGME features except interactive editing (coming soon).
-   Callback functions may be written in pure Python.
+   all GPGME features.  Callback functions may be written in pure
+   Python.  Exceptions raised in callbacks are properly propagated.
 
  * Ability to sign, encrypt, decrypt, and verify data.
 
@@ -50,9 +52,7 @@ defined here -- they correspond directly to certain object types in GPGME
 for C.  For instance, the following C code:
 
 gpgme_ctx_t context;
-
 gpgme_new(&context);
-
 ...
 gpgme_op_encrypt(context, recp, 1, plain, cipher);
 
@@ -130,8 +130,6 @@ Version information: pyme.version
 Utilities: pyme.util
 
 Base classes are documented at pyme.core.
-Classes of pyme.util usually are not instantiated by users
-directly but return by methods of base classes.
 
 """
 
diff --git a/lang/python/pyme/core.py b/lang/python/pyme/core.py
index 4b3e08a..293df09 100644
--- a/lang/python/pyme/core.py
+++ b/lang/python/pyme/core.py
@@ -1,3 +1,4 @@
+# Copyright (C) 2016 g10 Code GmbH
 # Copyright (C) 2004,2008 Igor Belyi <belyi at users.sourceforge.net>
 # Copyright (C) 2002 John Goerzen <jgoerzen at complete.org>
 #
@@ -15,14 +16,25 @@
 #    License along with this library; if not, write to the Free Software
 #    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307  USA
 
+"""Core functionality
+
+Core functionality of GPGME wrapped in a object-oriented fashion.
+Provides the 'Context' class for performing cryptographic operations,
+and the 'Data' class describing buffers of data.
+
+"""
+
 import weakref
 from . import pygpgme
 from .errors import errorcheck, GPGMEError
 from . import errors
 
 class GpgmeWrapper(object):
-    """Base class all Pyme wrappers for GPGME functionality.  Not to be
-    instantiated directly."""
+    """Base wrapper class
+
+    Not to be instantiated directly.
+
+    """
 
     def __init__(self, wrapped):
         self._callback_excinfo = None
@@ -66,7 +78,7 @@ class GpgmeWrapper(object):
         raise NotImplementedError()
 
     def __getattr__(self, key):
-        """On-the-fly function generation."""
+        """On-the-fly generation of wrapper methods."""
         if key[0] == '_' or self._getnameprepend() == None:
             return None
         name = self._getnameprepend() + key
@@ -98,15 +110,17 @@ class GpgmeWrapper(object):
         return wrapper
 
 class Context(GpgmeWrapper):
-    """From the GPGME C documentation:
+    """Context for cryptographic operations
+
+    All cryptographic operations in GPGME are performed within a
+    context, which contains the internal state of the operation as
+    well as configuration parameters.  By using several contexts you
+    can run several cryptographic operations in parallel, with
+    different configuration.
 
-    * All cryptographic operations in GPGME are performed within a
-    * context, which contains the internal state of the operation as well as
-    * configuration parameters.  By using several contexts you can run
-    * several cryptographic operations in parallel, with different
-    * configuration.
+    Access to a context must be synchronized.
 
-    Thus, this is the place that you will usually start."""
+    """
 
     def _getctype(self):
         return 'gpgme_ctx_t'
@@ -348,18 +362,21 @@ class Context(GpgmeWrapper):
         errorcheck(result)
 
 class Data(GpgmeWrapper):
-    """From the GPGME C manual:
+    """Data buffer
 
-* A lot of data has to be exchanged between the user and the crypto
-* engine, like plaintext messages, ciphertext, signatures and information
-* about the keys.  The technical details about exchanging the data
-* information are completely abstracted by GPGME.  The user provides and
-* receives the data via `gpgme_data_t' objects, regardless of the
-* communication protocol between GPGME and the crypto engine in use.
+    A lot of data has to be exchanged between the user and the crypto
+    engine, like plaintext messages, ciphertext, signatures and
+    information about the keys.  The technical details about
+    exchanging the data information are completely abstracted by
+    GPGME.  The user provides and receives the data via `gpgme_data_t'
+    objects, regardless of the communication protocol between GPGME
+    and the crypto engine in use.
 
-        This Data class is the implementation of the GpgmeData objects.
+    This Data class is the implementation of the GpgmeData objects.
 
-        Please see the information about __init__ for instantiation."""
+    Please see the information about __init__ for instantiation.
+
+    """
 
     def _getctype(self):
         return 'gpgme_data_t'
@@ -393,15 +410,31 @@ class Data(GpgmeWrapper):
 
         (read_cb, write_cb, seek_cb, release_cb[, hook])
 
-        where func is a callback function taking two arguments (count,
-        hook) and returning a string of read data, or None on EOF.
-        This will supply the read() method for the system.
+        where the first four items are functions implementing reading,
+        writing, seeking the data, and releasing any resources once
+        the data object is deallocated.  The functions must match the
+        following prototypes:
+
+            def read(amount, hook=None):
+                return <a b"bytes" object>
+
+            def write(data, hook=None):
+                return <the number of bytes written>
+
+            def seek(offset, whence, hook=None):
+                return <the new file position>
+
+            def release(hook=None):
+                <return value and exceptions are ignored>
+
+        The functions may be bound methods.  In that case, you can
+        simply use the 'self' reference instead of using a hook.
 
         If file is specified without any other arguments, then
         it must be a filename, and the object will be initialized from
         that file.
 
-        Any other use will result in undefined or erroneous behavior."""
+        """
         super().__init__(None)
         self.data_cbs = None
 
@@ -488,9 +521,10 @@ class Data(GpgmeWrapper):
         """This wraps the GPGME gpgme_data_new_from_filepart() function.
         The argument "file" may be:
 
-        1. a string specifying a file name, or
-        3. a a file-like object. supporting the fileno() call and the mode
-           attribute."""
+        * a string specifying a file name, or
+        * a file-like object supporting the fileno() and the mode attribute.
+
+        """
 
         tmp = pygpgme.new_gpgme_data_t_p()
         filename = None

commit 7eef399d89d4c3877cb795ed5ba45ecb241e67be
Author: Justus Winter <justus at g10code.com>
Date:   Thu Jun 9 12:38:50 2016 +0200

    python: Get version information from the build system.
    
    * configure.ac: Generate 'setup.py' and 'version.py'.
    * lang/python/Makefile.am: Use generated setup script.
    * lang/python/pyme/version.py: Turn it into a template, and get
    version information from the build system.  Also drop some variables.
    * lang/python/setup.py: Likewise.  This way we can avoid importing the
    version module, which is frowned upon and actually caused a problem.
    
    Signed-off-by: Justus Winter <justus at g10code.com>

diff --git a/configure.ac b/configure.ac
index b84b04b..4269540 100644
--- a/configure.ac
+++ b/configure.ac
@@ -775,7 +775,10 @@ AC_CONFIG_FILES(lang/qt/tests/Makefile)
 AC_CONFIG_FILES([lang/Makefile lang/cl/Makefile lang/cl/gpgme.asd])
 AM_COND_IF([HAVE_DOXYGEN], [AC_CONFIG_FILES([lang/qt/doc/Doxyfile])])
 AC_CONFIG_FILES(lang/qt/doc/Makefile)
-AC_CONFIG_FILES([lang/python/Makefile lang/python/tests/Makefile])
+AC_CONFIG_FILES([lang/python/Makefile
+		 lang/python/setup.py
+		 lang/python/pyme/version.py
+		 lang/python/tests/Makefile])
 AC_OUTPUT
 
 echo "
diff --git a/lang/python/Makefile.am b/lang/python/Makefile.am
index 46f45d0..2c84f2b 100644
--- a/lang/python/Makefile.am
+++ b/lang/python/Makefile.am
@@ -40,7 +40,8 @@ gpgme_wrap.c pyme/pygpgme.py: gpgme.i errors.i gpgme.h copystamp
 	  $<
 
 all-local: gpgme_wrap.c pyme/pygpgme.py copystamp
-	CFLAGS="$(CFLAGS)" $(PYTHON) $(srcdir)/setup.py build --verbose
+	CFLAGS="$(CFLAGS)" \
+	  $(PYTHON) setup.py build --verbose
 
 clean-local:
 	rm -rf -- build gpgme.h errors.i gpgme_wrap.c pyme/pygpgme.py \
@@ -50,7 +51,7 @@ clean-local:
 	fi
 
 install-exec-local:
-	$(PYTHON) $(srcdir)/setup.py install \
+	$(PYTHON) setup.py install \
 	  --prefix $(DESTDIR)$(prefix) \
 	  --record $(DESTDIR)$(pythondir)/pyme/install_files.txt \
 	  --verbose
diff --git a/lang/python/pyme/version.py b/lang/python/pyme/version.py
deleted file mode 100644
index b60f50c..0000000
--- a/lang/python/pyme/version.py
+++ /dev/null
@@ -1,42 +0,0 @@
-
-productname = 'pyme'
-versionstr = "0.9.1"
-revno = int('$Rev: 281 $'[6:-2])
-revstr = "Rev %d" % revno
-datestr = '$Date$'
-
-versionlist = versionstr.split(".")
-major = versionlist[0]
-minor = versionlist[1]
-patch = versionlist[2]
-copyright = "Copyright (C) 2015 Ben McGinnes, 2014-2015 Martin Albrecht, 2004-2008 Igor Belyi, 2002 John Goerzen"
-author = "Ben McGinnes"
-author_email = "ben at adversary.org"
-description = "Python 3 support for GPGME GnuPG cryptography library"
-bigcopyright = """%(productname)s %(versionstr)s (%(revstr)s)
-%(copyright)s <%(author_email)s>""" % locals()
-
-banner = bigcopyright + """
-This software comes with ABSOLUTELY NO WARRANTY; see the file
-COPYING for details.  This is free software, and you are welcome
-to distribute it under the conditions laid out in COPYING."""
-
-homepage = "https://gnupg.org"
-license = """Copyright (C) 2015 Ben McGinnes <ben at adversary.org>
-Copyright (C) 2014, 2015 Martin Albrecht <martinralbrecht at googlemail.com>
-Copyright (C) 2004, 2008 Igor Belyi <belyi at users.sourceforge.net>
-Copyright (C) 2002 John Goerzen <jgoerzen at complete.org>
-
-This library is free software; you can redistribute it and/or
-modify it under the terms of the GNU Lesser General Public
-License as published by the Free Software Foundation; either
-version 2.1 of the License, or (at your option) any later version.
-
-This library 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 library; if not, write to the Free Software
-Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307  USA"""
diff --git a/lang/python/pyme/version.py.in b/lang/python/pyme/version.py.in
new file mode 100644
index 0000000..764bf69
--- /dev/null
+++ b/lang/python/pyme/version.py.in
@@ -0,0 +1,62 @@
+# Copyright (C) 2016 g10 Code GmbH
+# Copyright (C) 2015 Ben McGinnes <ben at adversary.org>
+# Copyright (C) 2004 Igor Belyi <belyi at users.sourceforge.net>
+#
+# This library is free software; you can redistribute it and/or
+# modify it under the terms of the GNU Lesser General Public
+# License as published by the Free Software Foundation; either
+# version 2.1 of the License, or (at your option) any later version.
+#
+# This library 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 library; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307  USA
+
+productname = 'pyme'
+versionstr = "@VERSION@"
+
+# XXX: Do we want to embed such information?
+#revno = int('$Rev: 281 $'[6:-2])
+#revstr = "Rev %d" % revno
+
+versionlist = versionstr.split(".")
+major = versionlist[0]
+minor = versionlist[1]
+patch = versionlist[2]
+
+copyright = """\
+Copyright (C) 2016 g10 Code GmbH
+Copyright (C) 2015 Ben McGinnes
+Copyright (C) 2014-2015 Martin Albrecht
+Copyright (C) 2004-2008 Igor Belyi
+Copyright (C) 2002 John Goerzen"""
+
+author = "The GnuPG hackers"
+author_email = "gnupg-devel at gnupg.org"
+
+description = "Python 3 support for GPGME GnuPG cryptography library"
+homepage = "https://gnupg.org"
+
+license = """Copyright (C) 2016 g10 Code GmbH
+Copyright (C) 2015 Ben McGinnes <ben at adversary.org>
+Copyright (C) 2014, 2015 Martin Albrecht <martinralbrecht at googlemail.com>
+Copyright (C) 2004, 2008 Igor Belyi <belyi at users.sourceforge.net>
+Copyright (C) 2002 John Goerzen <jgoerzen at complete.org>
+
+This library is free software; you can redistribute it and/or
+modify it under the terms of the GNU Lesser General Public
+License as published by the Free Software Foundation; either
+version 2.1 of the License, or (at your option) any later version.
+
+This library 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 library; if not, write to the Free Software
+Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307  USA"""
diff --git a/lang/python/setup.py b/lang/python/setup.py.in
similarity index 87%
rename from lang/python/setup.py
rename to lang/python/setup.py.in
index 0d90403..9e6e008 100755
--- a/lang/python/setup.py
+++ b/lang/python/setup.py.in
@@ -1,8 +1,6 @@
 #!/usr/bin/env python3
 
-
-# Module: installer
-# COPYRIGHT #
+# Copyright (C) 2016 g10 Code GmbH
 # Copyright (C) 2004 Igor Belyi <belyi at users.sourceforge.net>
 # Copyright (C) 2002 John Goerzen <jgoerzen at complete.org>
 #
@@ -19,15 +17,11 @@
 #    You should have received a copy of the GNU Lesser General Public
 #    License along with this library; if not, write to the Free Software
 #    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307  USA
-# END OF COPYRIGHT #
 
 from distutils.core import setup, Extension
 import os, os.path, sys
 import subprocess
 
-sys.path.insert(0, os.path.dirname(__file__))
-import pyme.version
-
 def getconfig(what):
     confdata = subprocess.Popen(["../../src/gpgme-config", "--%s" % what],
                                 stdout=subprocess.PIPE).communicate()[0]
@@ -80,14 +74,13 @@ swige = Extension("pyme._pygpgme", ["gpgme_wrap.c", "helpers.c"],
                   extra_link_args = libs)
 
 setup(name = "pyme",
-      version=pyme.version.versionstr,
-      description=pyme.version.description,
-      author=pyme.version.author,
-      author_email=pyme.version.author_email,
-      url=pyme.version.homepage,
+      version="@VERSION@",
+      description='Python bindings for GPGME GnuPG cryptography library',
+      author='The GnuPG hackers',
+      author_email='gnupg-devel at gnupg.org',
+      url='https://www.gnupg.org',
       ext_modules=[swige],
       packages = ['pyme', 'pyme.constants', 'pyme.constants.data',
                   'pyme.constants.keylist', 'pyme.constants.sig'],
-      license=pyme.version.copyright + \
-                ", Licensed under the GPL version 2 and the LGPL version 2.1"
+      license="LGPL2.1+ (the library), GPL2+ (tests and examples)"
 )

commit a852f99a0ac9dc7f7493b403f811f5f7518fae40
Author: Justus Winter <justus at g10code.com>
Date:   Mon Jun 13 18:35:47 2016 +0200

    python: Fix exception leak.
    
    * lang/python/helpers.c (pygpgme_stash_callback_exception): Fix leak.
    
    Signed-off-by: Justus Winter <justus at g10code.com>

diff --git a/lang/python/helpers.c b/lang/python/helpers.c
index 0033ef0..810423d 100644
--- a/lang/python/helpers.c
+++ b/lang/python/helpers.c
@@ -122,6 +122,7 @@ static void pygpgme_stash_callback_exception(PyObject *weak_self)
     }
   else
     PyObject_SetAttrString(self, EXCINFO, excinfo);
+  Py_DECREF(excinfo);
 }
 
 PyObject *pygpgme_raise_callback_exception(PyObject *self)

commit 3bacce03e60dc45cc2da99a2f5c504612202e802
Author: Justus Winter <justus at g10code.com>
Date:   Tue Jun 14 16:54:26 2016 +0200

    python: Fix license.
    
    Other parts of the build system are also LGPLed.
    
    * lang/python/Makefile.am: Fix license.
    
    Signed-off-by: Justus Winter <justus at g10code.com>

diff --git a/lang/python/Makefile.am b/lang/python/Makefile.am
index a9b39e7..46f45d0 100644
--- a/lang/python/Makefile.am
+++ b/lang/python/Makefile.am
@@ -4,9 +4,9 @@
 # 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.
+# under the terms of the GNU Lesser General Public License as
+# published by the Free Software Foundation; either version 2.1 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

-----------------------------------------------------------------------

Summary of changes:
 configure.ac                          |   5 +-
 lang/python/Makefile.am               |  22 ++-
 lang/python/README                    |  57 +++++++
 lang/python/README.rst                |  48 ------
 lang/python/gpgme.i                   | 215 +++++++++++++++++++++---
 lang/python/helpers.c                 | 291 ++++++++++++++++++++++++---------
 lang/python/helpers.h                 |  24 +--
 lang/python/pyme/__init__.py          |  50 +++---
 lang/python/pyme/core.py              | 296 ++++++++++++++++++++++------------
 lang/python/pyme/results.py           | 116 +++++++++++++
 lang/python/pyme/version.py           |  42 -----
 lang/python/pyme/version.py.in        |  62 +++++++
 lang/python/{setup.py => setup.py.in} |  21 +--
 lang/python/tests/t-idiomatic.py      |  35 +++-
 14 files changed, 927 insertions(+), 357 deletions(-)
 create mode 100644 lang/python/README
 delete mode 100644 lang/python/README.rst
 create mode 100644 lang/python/pyme/results.py
 delete mode 100644 lang/python/pyme/version.py
 create mode 100644 lang/python/pyme/version.py.in
 rename lang/python/{setup.py => setup.py.in} (87%)


hooks/post-receive
-- 
GnuPG Made Easy
http://git.gnupg.org




More information about the Gnupg-commits mailing list