python: Adapt to 'gpgme_op_interact'.
[gpgme.git] / lang / python / pyme / core.py
index c090331..88a086b 100644 (file)
@@ -24,11 +24,18 @@ and the 'Data' class describing buffers of data.
 
 """
 
+from __future__ import absolute_import, print_function, unicode_literals
+del absolute_import, print_function, unicode_literals
+
+import re
+import os
+import warnings
 import weakref
-from . import pygpgme
+from . import gpgme
 from .errors import errorcheck, GPGMEError
 from . import constants
 from . import errors
+from . import util
 
 class GpgmeWrapper(object):
     """Base wrapper class
@@ -42,7 +49,8 @@ class GpgmeWrapper(object):
         self.wrapped = wrapped
 
     def __repr__(self):
-        return '<{}/{!r}>'.format(super().__repr__(), self.wrapped)
+        return '<{}/{!r}>'.format(super(GpgmeWrapper, self).__repr__(),
+                                  self.wrapped)
 
     def __str__(self):
         acc = ['{}.{}'.format(__name__, self.__class__.__name__)]
@@ -90,9 +98,9 @@ class GpgmeWrapper(object):
     _boolean_properties = set()
 
     def __wrap_boolean_property(self, key, do_set=False, value=None):
-        get_func = getattr(pygpgme,
+        get_func = getattr(gpgme,
                            "{}get_{}".format(self._cprefix, key))
-        set_func = getattr(pygpgme,
+        set_func = getattr(gpgme,
                            "{}set_{}".format(self._cprefix, key))
         def get(slf):
             return bool(get_func(slf.wrapped))
@@ -107,6 +115,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:
@@ -116,30 +125,31 @@ class GpgmeWrapper(object):
             return self.__wrap_boolean_property(key)
 
         name = self._cprefix + key
-        func = getattr(pygpgme, name)
+        func = getattr(gpgme, 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)
+                    gpgme.pyme_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)
+                    gpgme.pyme_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
 
@@ -148,7 +158,7 @@ class GpgmeWrapper(object):
         if key in self._boolean_properties:
             self.__wrap_boolean_property(key, True, value)
         else:
-            super().__setattr__(key, value)
+            super(GpgmeWrapper, self).__setattr__(key, value)
 
 class Context(GpgmeWrapper):
     """Context for cryptographic operations
@@ -163,6 +173,403 @@ class Context(GpgmeWrapper):
 
     """
 
+    def __init__(self, armor=False, textmode=False, offline=False,
+                 signers=[], pinentry_mode=constants.PINENTRY_MODE_DEFAULT,
+                 protocol=constants.PROTOCOL_OpenPGP,
+                 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)
+        protocol       -- protocol to use (default PROTOCOL_OpenPGP)
+
+        """
+        if wrapped:
+            self.own = False
+        else:
+            tmp = gpgme.new_gpgme_ctx_t_p()
+            errorcheck(gpgme.gpgme_new(tmp))
+            wrapped = gpgme.gpgme_ctx_t_p_value(tmp)
+            gpgme.delete_gpgme_ctx_t_p(tmp)
+            self.own = True
+        super(Context, self).__init__(wrapped)
+        self.armor = armor
+        self.textmode = textmode
+        self.offline = offline
+        self.signers = signers
+        self.pinentry_mode = pinentry_mode
+        self.protocol = protocol
+
+    def encrypt(self, plaintext, recipients=[], sign=True, sink=None,
+                passphrase=None, always_trust=False, add_encrypt_to=False,
+                prepare=False, expect_sign=False, compress=True):
+        """Encrypt data
+
+        Encrypt the given plaintext for the given recipients.  If the
+        list of recipients is empty, the data is encrypted
+        symmetrically with a passphrase.
+
+        The passphrase can be given as parameter, using a callback
+        registered at the context, or out-of-band via pinentry.
+
+        Keyword arguments:
+        recipients     -- list of keys to encrypt to
+        sign           -- sign plaintext (default True)
+        sink           -- write result to sink instead of returning it
+        passphrase     -- for symmetric encryption
+        always_trust   -- always trust the keys (default False)
+        add_encrypt_to -- encrypt to configured additional keys (default False)
+        prepare                -- (ui) prepare for encryption (default False)
+        expect_sign    -- (ui) prepare for signing (default False)
+        compress       -- compress plaintext (default True)
+
+        Returns:
+        ciphertext     -- the encrypted data (or None if sink is given)
+        result         -- additional information about the encryption
+        sign_result    -- additional information about the signature(s)
+
+        Raises:
+        InvalidRecipients -- if encryption using a particular key failed
+        InvalidSigners -- if signing using a particular key failed
+        GPGMEError     -- as signaled by the underlying library
+
+        """
+        ciphertext = sink if sink else Data()
+        flags = 0
+        flags |= always_trust * constants.ENCRYPT_ALWAYS_TRUST
+        flags |= (not add_encrypt_to) * constants.ENCRYPT_NO_ENCRYPT_TO
+        flags |= prepare * constants.ENCRYPT_PREPARE
+        flags |= expect_sign * constants.ENCRYPT_EXPECT_SIGN
+        flags |= (not compress) * constants.ENCRYPT_NO_COMPRESS
+
+        if passphrase != None:
+            old_pinentry_mode = self.pinentry_mode
+            old_passphrase_cb = getattr(self, '_passphrase_cb', None)
+            self.pinentry_mode = constants.PINENTRY_MODE_LOOPBACK
+            def passphrase_cb(hint, desc, prev_bad, hook=None):
+                return passphrase
+            self.set_passphrase_cb(passphrase_cb)
+
+        try:
+            if sign:
+                self.op_encrypt_sign(recipients, flags, plaintext, ciphertext)
+            else:
+                self.op_encrypt(recipients, flags, plaintext, ciphertext)
+        except errors.GPGMEError as e:
+            if e.getcode() == errors.UNUSABLE_PUBKEY:
+                result = self.op_encrypt_result()
+                if result.invalid_recipients:
+                    raise errors.InvalidRecipients(result.invalid_recipients)
+            if e.getcode() == errors.UNUSABLE_SECKEY:
+                sig_result = self.op_sign_result()
+                if sig_result.invalid_signers:
+                    raise errors.InvalidSigners(sig_result.invalid_signers)
+            raise
+        finally:
+            if passphrase != None:
+                self.pinentry_mode = old_pinentry_mode
+                if old_passphrase_cb:
+                    self.set_passphrase_cb(*old_passphrase_cb[1:])
+
+        result = self.op_encrypt_result()
+        assert not result.invalid_recipients
+        sig_result = self.op_sign_result() if sign else None
+        assert not sig_result or not sig_result.invalid_signers
+
+        cipherbytes = None
+        if not sink:
+            ciphertext.seek(0, os.SEEK_SET)
+            cipherbytes = ciphertext.read()
+        return cipherbytes, result, sig_result
+
+    def decrypt(self, ciphertext, sink=None, passphrase=None, verify=True):
+        """Decrypt data
+
+        Decrypt the given ciphertext and verify any signatures.  If
+        VERIFY is an iterable of keys, the ciphertext must be signed
+        by all those keys, otherwise an error is raised.
+
+        If the ciphertext is symmetrically encrypted using a
+        passphrase, that passphrase can be given as parameter, using a
+        callback registered at the context, or out-of-band via
+        pinentry.
+
+        Keyword arguments:
+        sink           -- write result to sink instead of returning it
+        passphrase     -- for symmetric decryption
+        verify         -- check signatures (default True)
+
+        Returns:
+        plaintext      -- the decrypted data (or None if sink is given)
+        result         -- additional information about the decryption
+        verify_result  -- additional information about the signature(s)
+
+        Raises:
+        UnsupportedAlgorithm -- if an unsupported algorithm was used
+        BadSignatures  -- if a bad signature is encountered
+        MissingSignatures -- if expected signatures are missing or bad
+        GPGMEError     -- as signaled by the underlying library
+
+        """
+        plaintext = sink if sink else Data()
+
+        if passphrase != None:
+            old_pinentry_mode = self.pinentry_mode
+            old_passphrase_cb = getattr(self, '_passphrase_cb', None)
+            self.pinentry_mode = constants.PINENTRY_MODE_LOOPBACK
+            def passphrase_cb(hint, desc, prev_bad, hook=None):
+                return passphrase
+            self.set_passphrase_cb(passphrase_cb)
+
+        try:
+            if verify:
+                self.op_decrypt_verify(ciphertext, plaintext)
+            else:
+                self.op_decrypt(ciphertext, plaintext)
+        finally:
+            if passphrase != None:
+                self.pinentry_mode = old_pinentry_mode
+                if old_passphrase_cb:
+                    self.set_passphrase_cb(*old_passphrase_cb[1:])
+
+        result = self.op_decrypt_result()
+        verify_result = self.op_verify_result() if verify else None
+        if result.unsupported_algorithm:
+            raise errors.UnsupportedAlgorithm(result.unsupported_algorithm)
+
+        if verify:
+            if any(s.status != errors.NO_ERROR
+                   for s in verify_result.signatures):
+                raise errors.BadSignatures(verify_result)
+
+        if verify and verify != True:
+            missing = list()
+            for key in verify:
+                ok = False
+                for subkey in key.subkeys:
+                    for sig in verify_result.signatures:
+                        if sig.summary & constants.SIGSUM_VALID == 0:
+                            continue
+                        if subkey.can_sign and subkey.fpr == sig.fpr:
+                            ok = True
+                            break
+                    if ok:
+                        break
+                if not ok:
+                    missing.append(key)
+            if missing:
+                raise errors.MissingSignatures(verify_result, missing)
+
+        plainbytes = None
+        if not sink:
+            plaintext.seek(0, os.SEEK_SET)
+            plainbytes = plaintext.read()
+        return plainbytes, result, verify_result
+
+    def sign(self, data, sink=None, mode=constants.SIG_MODE_NORMAL):
+        """Sign data
+
+        Sign the given data with either the configured default local
+        key, or the 'signers' keys of this context.
+
+        Keyword arguments:
+        mode           -- signature mode (default: normal, see below)
+        sink           -- write result to sink instead of returning it
+
+        Returns:
+        either
+          signed_data  -- encoded data and signature (normal mode)
+          signature    -- only the signature data (detached mode)
+          cleartext    -- data and signature as text (cleartext mode)
+            (or None if sink is given)
+        result         -- additional information about the signature(s)
+
+        Raises:
+        InvalidSigners -- if signing using a particular key failed
+        GPGMEError     -- as signaled by the underlying library
+
+        """
+        signeddata = sink if sink else Data()
+
+        try:
+            self.op_sign(data, signeddata, mode)
+        except errors.GPGMEError as e:
+            if e.getcode() == errors.UNUSABLE_SECKEY:
+                result = self.op_sign_result()
+                if result.invalid_signers:
+                    raise errors.InvalidSigners(result.invalid_signers)
+            raise
+
+        result = self.op_sign_result()
+        assert not result.invalid_signers
+
+        signedbytes = None
+        if not sink:
+            signeddata.seek(0, os.SEEK_SET)
+            signedbytes = signeddata.read()
+        return signedbytes, result
+
+    def verify(self, signed_data, signature=None, sink=None, verify=[]):
+        """Verify signatures
+
+        Verify signatures over data.  If VERIFY is an iterable of
+        keys, the ciphertext must be signed by all those keys,
+        otherwise an error is raised.
+
+        Keyword arguments:
+        signature      -- detached signature data
+        sink           -- write result to sink instead of returning it
+
+        Returns:
+        data           -- the plain data
+            (or None if sink is given, or we verified a detached signature)
+        result         -- additional information about the signature(s)
+
+        Raises:
+        BadSignatures  -- if a bad signature is encountered
+        MissingSignatures -- if expected signatures are missing or bad
+        GPGMEError     -- as signaled by the underlying library
+
+        """
+        if signature:
+            # Detached signature, we don't return the plain text.
+            data = None
+        else:
+            data = sink if sink else Data()
+
+        if signature:
+            self.op_verify(signature, signed_data, None)
+        else:
+            self.op_verify(signed_data, None, data)
+
+        result = self.op_verify_result()
+        if any(s.status != errors.NO_ERROR for s in result.signatures):
+            raise errors.BadSignatures(result)
+
+        missing = list()
+        for key in verify:
+            ok = False
+            for subkey in key.subkeys:
+                for sig in result.signatures:
+                    if sig.summary & constants.SIGSUM_VALID == 0:
+                        continue
+                    if subkey.can_sign and subkey.fpr == sig.fpr:
+                        ok = True
+                        break
+                if ok:
+                    break
+            if not ok:
+                missing.append(key)
+        if missing:
+            raise errors.MissingSignatures(result, missing)
+
+        plainbytes = None
+        if data and not sink:
+            data.seek(0, os.SEEK_SET)
+            plainbytes = data.read()
+        return plainbytes, result
+
+    def keylist(self, pattern=None, secret=False):
+        """List keys
+
+        Keyword arguments:
+        pattern        -- return keys matching pattern (default: all keys)
+        secret -- return only secret keys
+
+        Returns:
+                -- an iterator returning key objects
+
+        Raises:
+        GPGMEError     -- as signaled by the underlying library
+        """
+        return self.op_keylist_all(pattern, secret)
+
+    def assuan_transact(self, command,
+                        data_cb=None, inquire_cb=None, status_cb=None):
+        """Issue a raw assuan command
+
+        This function can be used to issue a raw assuan command to the
+        engine.
+
+        If command is a string or bytes, it will be used as-is.  If it
+        is an iterable of strings, it will be properly escaped and
+        joined into an well-formed assuan command.
+
+        Keyword arguments:
+        data_cb                -- a callback receiving data lines
+        inquire_cb     -- a callback providing more information
+        status_cb      -- a callback receiving status lines
+
+        Returns:
+        result         -- the result of command as GPGMEError
+
+        Raises:
+        GPGMEError     -- as signaled by the underlying library
+
+        """
+
+        if isinstance(command, (str, bytes)):
+            cmd = command
+        else:
+            cmd = " ".join(util.percent_escape(f) for f in command)
+
+        errptr = gpgme.new_gpgme_error_t_p()
+
+        err = gpgme.gpgme_op_assuan_transact_ext(
+            self.wrapped,
+            cmd,
+            (weakref.ref(self), data_cb) if data_cb else None,
+            (weakref.ref(self), inquire_cb) if inquire_cb else None,
+            (weakref.ref(self), status_cb) if status_cb else None,
+            errptr)
+
+        if self._callback_excinfo:
+            gpgme.pyme_raise_callback_exception(self)
+
+        errorcheck(err)
+
+        status = gpgme.gpgme_error_t_p_value(errptr)
+        gpgme.delete_gpgme_error_t_p(errptr)
+
+        return GPGMEError(status) if status != 0 else None
+
+    def interact(self, key, func, sink=None, flags=0, fnc_value=None):
+        """Interact with the engine
+
+        This method can be used to edit keys and cards interactively.
+        KEY is the key to edit, FUNC is called repeatedly with two
+        unicode arguments, 'keyword' and 'args'.  See the GPGME manual
+        for details.
+
+        Keyword arguments:
+        sink           -- if given, additional output is written here
+        flags          -- use constants.INTERACT_CARD to edit a card
+
+        Raises:
+        GPGMEError     -- as signaled by the underlying library
+
+        """
+        if key == None:
+            raise ValueError("First argument cannot be None")
+
+        if sink == None:
+            sink = Data()
+
+        if fnc_value:
+            opaquedata = (weakref.ref(self), func, fnc_value)
+        else:
+            opaquedata = (weakref.ref(self), func)
+
+        result = gpgme.gpgme_op_interact(self.wrapped, key, flags,
+                                         opaquedata, sink)
+        if self._callback_excinfo:
+            gpgme.pyme_raise_callback_exception(self)
+        errorcheck(result)
+
     @property
     def signers(self):
         """Keys used for signing"""
@@ -186,58 +593,50 @@ class Context(GpgmeWrapper):
     def pinentry_mode(self, value):
         self.set_pinentry_mode(value)
 
+    @property
+    def protocol(self):
+        """Protocol to use"""
+        return self.get_protocol()
+    @protocol.setter
+    def protocol(self, value):
+        errorcheck(gpgme.gpgme_engine_check_version(value))
+        self.set_protocol(value)
+
     _ctype = 'gpgme_ctx_t'
     _cprefix = 'gpgme_'
 
     def _errorcheck(self, name):
         """This function should list all functions returning gpgme_error_t"""
-        if (name.startswith('gpgme_op_') and \
-            not name.endswith('_result')) or \
-            name == 'gpgme_signers_add' or \
-            name == 'gpgme_set_locale' or \
-            name == 'gpgme_set_keylist_mode' or \
-            name == 'gpgme_set_protocol':
-            return 1
-        return 0
+        return ((name.startswith('gpgme_op_')
+                 and not name.endswith('_result'))
+                or name in {
+                    'gpgme_set_ctx_flag',
+                    'gpgme_set_protocol',
+                    'gpgme_set_sub_protocol',
+                    'gpgme_set_keylist_mode',
+                    'gpgme_set_pinentry_mode',
+                    'gpgme_set_locale',
+                    'gpgme_set_engine_info',
+                    'gpgme_signers_add',
+                    'gpgme_get_sig_key',
+                    'gpgme_sig_notation_add',
+                    'gpgme_cancel',
+                    'gpgme_cancel_async',
+                    'gpgme_cancel_get_key',
+                })
 
     _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:
-            tmp = pygpgme.new_gpgme_ctx_t_p()
-            errorcheck(pygpgme.gpgme_new(tmp))
-            wrapped = pygpgme.gpgme_ctx_t_p_value(tmp)
-            pygpgme.delete_gpgme_ctx_t_p(tmp)
-            self.own = True
-        super().__init__(wrapped)
-        self.armor = armor
-        self.textmode = textmode
-        self.offline = offline
-        self.signers = signers
-        self.pinentry_mode = pinentry_mode
 
     def __del__(self):
-        if not pygpgme:
-            # At interpreter shutdown, pygpgme is set to NONE.
+        if not gpgme:
+            # At interpreter shutdown, gpgme is set to NONE.
             return
 
         self._free_passcb()
         self._free_progresscb()
         self._free_statuscb()
-        if self.own and self.wrapped and pygpgme.gpgme_release:
-            pygpgme.gpgme_release(self.wrapped)
+        if self.own and self.wrapped and gpgme.gpgme_release:
+            gpgme.gpgme_release(self.wrapped)
             self.wrapped = None
 
     # Implement the context manager protocol.
@@ -252,54 +651,56 @@ class Context(GpgmeWrapper):
         while key:
             yield key
             key = self.op_keylist_next()
+        self.op_keylist_end()
 
     def op_keylist_next(self):
         """Returns the next key in the list created
         by a call to op_keylist_start().  The object returned
         is of type Key."""
-        ptr = pygpgme.new_gpgme_key_t_p()
+        ptr = gpgme.new_gpgme_key_t_p()
         try:
-            errorcheck(pygpgme.gpgme_op_keylist_next(self.wrapped, ptr))
-            key = pygpgme.gpgme_key_t_p_value(ptr)
+            errorcheck(gpgme.gpgme_op_keylist_next(self.wrapped, ptr))
+            key = gpgme.gpgme_key_t_p_value(ptr)
         except errors.GPGMEError as excp:
             key = None
             if excp.getcode() != errors.EOF:
                 raise excp
-        pygpgme.delete_gpgme_key_t_p(ptr)
+        gpgme.delete_gpgme_key_t_p(ptr)
         if key:
-            key.__del__ = lambda self: pygpgme.gpgme_key_unref(self)
+            key.__del__ = lambda self: gpgme.gpgme_key_unref(self)
             return key
 
     def get_key(self, fpr, secret):
         """Return the key corresponding to the fingerprint 'fpr'"""
-        ptr = pygpgme.new_gpgme_key_t_p()
-        errorcheck(pygpgme.gpgme_get_key(self.wrapped, fpr, ptr, secret))
-        key = pygpgme.gpgme_key_t_p_value(ptr)
-        pygpgme.delete_gpgme_key_t_p(ptr)
+        ptr = gpgme.new_gpgme_key_t_p()
+        errorcheck(gpgme.gpgme_get_key(self.wrapped, fpr, ptr, secret))
+        key = gpgme.gpgme_key_t_p_value(ptr)
+        gpgme.delete_gpgme_key_t_p(ptr)
         if key:
-            key.__del__ = lambda self: pygpgme.gpgme_key_unref(self)
+            key.__del__ = lambda self: gpgme.gpgme_key_unref(self)
             return key
 
     def op_trustlist_all(self, *args, **kwargs):
         self.op_trustlist_start(*args, **kwargs)
-        trust = self.ctx.op_trustlist_next()
+        trust = self.op_trustlist_next()
         while trust:
             yield trust
-            trust = self.ctx.op_trustlist_next()
+            trust = self.op_trustlist_next()
+        self.op_trustlist_end()
 
     def op_trustlist_next(self):
         """Returns the next trust item in the list created
         by a call to op_trustlist_start().  The object returned
         is of type TrustItem."""
-        ptr = pygpgme.new_gpgme_trust_item_t_p()
+        ptr = gpgme.new_gpgme_trust_item_t_p()
         try:
-            errorcheck(pygpgme.gpgme_op_trustlist_next(self.wrapped, ptr))
-            trust = pygpgme.gpgme_trust_item_t_p_value(ptr)
+            errorcheck(gpgme.gpgme_op_trustlist_next(self.wrapped, ptr))
+            trust = gpgme.gpgme_trust_item_t_p_value(ptr)
         except errors.GPGMEError as excp:
             trust = None
             if excp.getcode() != errors.EOF:
                 raise
-        pygpgme.delete_gpgme_trust_item_t_p(ptr)
+        gpgme.delete_gpgme_trust_item_t_p(ptr)
         return trust
 
     def set_passphrase_cb(self, func, hook=None):
@@ -323,10 +724,10 @@ class Context(GpgmeWrapper):
                 hookdata = (weakref.ref(self), func)
             else:
                 hookdata = (weakref.ref(self), func, hook)
-        pygpgme.pygpgme_set_passphrase_cb(self, hookdata)
+        gpgme.pyme_set_passphrase_cb(self, hookdata)
 
     def _free_passcb(self):
-        if pygpgme.pygpgme_set_passphrase_cb:
+        if gpgme.pyme_set_passphrase_cb:
             self.set_passphrase_cb(None)
 
     def set_progress_cb(self, func, hook=None):
@@ -348,10 +749,10 @@ class Context(GpgmeWrapper):
                 hookdata = (weakref.ref(self), func)
             else:
                 hookdata = (weakref.ref(self), func, hook)
-        pygpgme.pygpgme_set_progress_cb(self, hookdata)
+        gpgme.pyme_set_progress_cb(self, hookdata)
 
     def _free_progresscb(self):
-        if pygpgme.pygpgme_set_progress_cb:
+        if gpgme.pyme_set_progress_cb:
             self.set_progress_cb(None)
 
     def set_status_cb(self, func, hook=None):
@@ -372,23 +773,45 @@ class Context(GpgmeWrapper):
                 hookdata = (weakref.ref(self), func)
             else:
                 hookdata = (weakref.ref(self), func, hook)
-        pygpgme.pygpgme_set_status_cb(self, hookdata)
+        gpgme.pyme_set_status_cb(self, hookdata)
 
     def _free_statuscb(self):
-        if pygpgme.pygpgme_set_status_cb:
+        if gpgme.pyme_set_status_cb:
             self.set_status_cb(None)
 
+    @property
+    def engine_info(self):
+        """Configuration of the engine currently in use"""
+        p = self.protocol
+        infos = [i for i in self.get_engine_info() if i.protocol == p]
+        assert len(infos) == 1
+        return infos[0]
+
     def get_engine_info(self):
-        """Returns this context specific engine info"""
-        return pygpgme.gpgme_ctx_get_engine_info(self.wrapped)
+        """Get engine configuration
 
-    def set_engine_info(self, proto, file_name, home_dir=None):
-        """Changes the configuration of the crypto engine implementing the
-    protocol 'proto' for the context. 'file_name' is the file name of
-    the executable program implementing this protocol. 'home_dir' is the
-    directory name of the configuration directory (engine's default is
-    used if omitted)."""
-        errorcheck(pygpgme.gpgme_ctx_set_engine_info(self.wrapped, proto, file_name, home_dir))
+        Returns information about all configured and installed
+        engines.
+
+        Returns:
+        infos          -- a list of engine infos
+
+        """
+        return gpgme.gpgme_ctx_get_engine_info(self.wrapped)
+
+    def set_engine_info(self, proto, file_name=None, home_dir=None):
+        """Change engine configuration
+
+        Changes the configuration of the crypto engine implementing
+        the protocol 'proto' for the context.
+
+        Keyword arguments:
+        file_name      -- engine program file name (unchanged if None)
+        home_dir       -- configuration directory (unchanged if None)
+
+        """
+        errorcheck(gpgme.gpgme_ctx_set_engine_info(
+            self.wrapped, proto, file_name, home_dir))
 
     def wait(self, hang):
         """Wait for asynchronous call to finish. Wait forever if hang is True.
@@ -397,25 +820,28 @@ class Context(GpgmeWrapper):
         Please read the GPGME manual for more information.
 
         """
-        ptr = pygpgme.new_gpgme_error_t_p()
-        pygpgme.gpgme_wait(self.wrapped, ptr, hang)
-        status = pygpgme.gpgme_error_t_p_value(ptr)
-        pygpgme.delete_gpgme_error_t_p(ptr)
+        ptr = gpgme.new_gpgme_error_t_p()
+        gpgme.gpgme_wait(self.wrapped, ptr, hang)
+        status = gpgme.gpgme_error_t_p_value(ptr)
+        gpgme.delete_gpgme_error_t_p(ptr)
         errorcheck(status)
 
     def op_edit(self, key, func, fnc_value, out):
-        """Start key editing using supplied callback function"""
-        if key == None:
-            raise ValueError("op_edit: First argument cannot be None")
-        if fnc_value:
-            opaquedata = (weakref.ref(self), func, fnc_value)
-        else:
-            opaquedata = (weakref.ref(self), func)
+        """Start key editing using supplied callback function
+
+        Note: This interface is deprecated and will be removed with
+        GPGME 1.8.  Please use .interact instead.  Furthermore, we
+        implement this using gpgme_op_interact, so callbacks will get
+        called with string keywords instead of numeric status
+        messages.  Code that is using constants.STATUS_X or
+        constants.status.X will continue to work, whereas code using
+        magic numbers will break as a result.
+
+        """
+        warnings.warn("Call to deprecated method op_edit.",
+                      category=DeprecationWarning)
+        return self.interact(key, func, sink=out, fnc_value=fnc_value)
 
-        result = pygpgme.gpgme_op_edit(self.wrapped, key, opaquedata, out)
-        if self._callback_excinfo:
-            pygpgme.pygpgme_raise_callback_exception(self)
-        errorcheck(result)
 
 class Data(GpgmeWrapper):
     """Data buffer
@@ -488,7 +914,7 @@ class Data(GpgmeWrapper):
         that file.
 
         """
-        super().__init__(None)
+        super(Data, self).__init__(None)
         self.data_cbs = None
 
         if cbs != None:
@@ -498,7 +924,7 @@ class Data(GpgmeWrapper):
         elif file != None and offset != None and length != None:
             self.new_from_filepart(file, offset, length)
         elif file != None:
-            if type(file) == type("x"):
+            if util.is_a_string(file):
                 self.new_from_file(file, copy)
             else:
                 self.new_from_fd(file)
@@ -506,14 +932,14 @@ class Data(GpgmeWrapper):
             self.new()
 
     def __del__(self):
-        if not pygpgme:
-            # At interpreter shutdown, pygpgme is set to NONE.
+        if not gpgme:
+            # At interpreter shutdown, gpgme is set to NONE.
             return
 
-        if self.wrapped != None and pygpgme.gpgme_data_release:
-            pygpgme.gpgme_data_release(self.wrapped)
+        if self.wrapped != None and gpgme.gpgme_data_release:
+            gpgme.gpgme_data_release(self.wrapped)
             if self._callback_excinfo:
-                pygpgme.pygpgme_raise_callback_exception(self)
+                gpgme.pyme_raise_callback_exception(self)
             self.wrapped = None
         self._free_datacbs()
 
@@ -527,40 +953,40 @@ class Data(GpgmeWrapper):
         self._data_cbs = None
 
     def new(self):
-        tmp = pygpgme.new_gpgme_data_t_p()
-        errorcheck(pygpgme.gpgme_data_new(tmp))
-        self.wrapped = pygpgme.gpgme_data_t_p_value(tmp)
-        pygpgme.delete_gpgme_data_t_p(tmp)
+        tmp = gpgme.new_gpgme_data_t_p()
+        errorcheck(gpgme.gpgme_data_new(tmp))
+        self.wrapped = gpgme.gpgme_data_t_p_value(tmp)
+        gpgme.delete_gpgme_data_t_p(tmp)
 
     def new_from_mem(self, string, copy=True):
-        tmp = pygpgme.new_gpgme_data_t_p()
-        errorcheck(pygpgme.gpgme_data_new_from_mem(tmp,string,len(string),copy))
-        self.wrapped = pygpgme.gpgme_data_t_p_value(tmp)
-        pygpgme.delete_gpgme_data_t_p(tmp)
+        tmp = gpgme.new_gpgme_data_t_p()
+        errorcheck(gpgme.gpgme_data_new_from_mem(tmp,string,len(string),copy))
+        self.wrapped = gpgme.gpgme_data_t_p_value(tmp)
+        gpgme.delete_gpgme_data_t_p(tmp)
 
     def new_from_file(self, filename, copy=True):
-        tmp = pygpgme.new_gpgme_data_t_p()
+        tmp = gpgme.new_gpgme_data_t_p()
         try:
-            errorcheck(pygpgme.gpgme_data_new_from_file(tmp, filename, copy))
+            errorcheck(gpgme.gpgme_data_new_from_file(tmp, filename, copy))
         except errors.GPGMEError as e:
             if e.getcode() == errors.INV_VALUE and not copy:
                 raise ValueError("delayed reads are not yet supported")
             else:
                 raise e
-        self.wrapped = pygpgme.gpgme_data_t_p_value(tmp)
-        pygpgme.delete_gpgme_data_t_p(tmp)
+        self.wrapped = gpgme.gpgme_data_t_p_value(tmp)
+        gpgme.delete_gpgme_data_t_p(tmp)
 
     def new_from_cbs(self, read_cb, write_cb, seek_cb, release_cb, hook=None):
-        tmp = pygpgme.new_gpgme_data_t_p()
+        tmp = gpgme.new_gpgme_data_t_p()
         if hook != None:
             hookdata = (weakref.ref(self),
                         read_cb, write_cb, seek_cb, release_cb, hook)
         else:
             hookdata = (weakref.ref(self),
                         read_cb, write_cb, seek_cb, release_cb)
-        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)
+        gpgme.pyme_data_new_from_cbs(self, hookdata, tmp)
+        self.wrapped = gpgme.gpgme_data_t_p_value(tmp)
+        gpgme.delete_gpgme_data_t_p(tmp)
 
     def new_from_filepart(self, file, offset, length):
         """This wraps the GPGME gpgme_data_new_from_filepart() function.
@@ -571,22 +997,22 @@ class Data(GpgmeWrapper):
 
         """
 
-        tmp = pygpgme.new_gpgme_data_t_p()
+        tmp = gpgme.new_gpgme_data_t_p()
         filename = None
         fp = None
 
-        if type(file) == type("x"):
+        if util.is_a_string(file):
             filename = file
         else:
-            fp = pygpgme.fdopen(file.fileno(), file.mode)
+            fp = gpgme.fdopen(file.fileno(), file.mode)
             if fp == None:
                 raise ValueError("Failed to open file from %s arg %s" % \
                       (str(type(file)), str(file)))
 
-        errorcheck(pygpgme.gpgme_data_new_from_filepart(tmp, filename, fp,
+        errorcheck(gpgme.gpgme_data_new_from_filepart(tmp, filename, fp,
                                                       offset, length))
-        self.wrapped = pygpgme.gpgme_data_t_p_value(tmp)
-        pygpgme.delete_gpgme_data_t_p(tmp)
+        self.wrapped = gpgme.gpgme_data_t_p_value(tmp)
+        gpgme.delete_gpgme_data_t_p(tmp)
 
     def new_from_fd(self, file):
         """This wraps the GPGME gpgme_data_new_from_fd() function.  The
@@ -594,10 +1020,10 @@ class Data(GpgmeWrapper):
         fileno() method.
 
         """
-        tmp = pygpgme.new_gpgme_data_t_p()
-        errorcheck(pygpgme.gpgme_data_new_from_fd(tmp, file.fileno()))
-        self.wrapped = pygpgme.gpgme_data_t_p_value(tmp)
-        pygpgme.delete_gpgme_data_t_p(tmp)
+        tmp = gpgme.new_gpgme_data_t_p()
+        errorcheck(gpgme.gpgme_data_new_from_fd(tmp, file.fileno()))
+        self.wrapped = gpgme.gpgme_data_t_p_value(tmp)
+        gpgme.delete_gpgme_data_t_p(tmp)
 
     def new_from_stream(self, file):
         """This wrap around gpgme_data_new_from_stream is an alias for
@@ -609,10 +1035,10 @@ class Data(GpgmeWrapper):
         """Write buffer given as string or bytes.
 
         If a string is given, it is implicitly encoded using UTF-8."""
-        written = pygpgme.gpgme_data_write(self.wrapped, buffer)
+        written = gpgme.gpgme_data_write(self.wrapped, buffer)
         if written < 0:
             if self._callback_excinfo:
-                pygpgme.pygpgme_raise_callback_exception(self)
+                gpgme.pyme_raise_callback_exception(self)
             else:
                 raise GPGMEError.fromSyserror()
         return written
@@ -630,10 +1056,10 @@ class Data(GpgmeWrapper):
 
         if size > 0:
             try:
-                result = pygpgme.gpgme_data_read(self.wrapped, size)
+                result = gpgme.gpgme_data_read(self.wrapped, size)
             except:
                 if self._callback_excinfo:
-                    pygpgme.pygpgme_raise_callback_exception(self)
+                    gpgme.pyme_raise_callback_exception(self)
                 else:
                     raise
             return result
@@ -641,10 +1067,10 @@ class Data(GpgmeWrapper):
             chunks = []
             while True:
                 try:
-                    result = pygpgme.gpgme_data_read(self.wrapped, 4096)
+                    result = gpgme.gpgme_data_read(self.wrapped, 4096)
                 except:
                     if self._callback_excinfo:
-                        pygpgme.pygpgme_raise_callback_exception(self)
+                        gpgme.pyme_raise_callback_exception(self)
                     else:
                         raise
                 if len(result) == 0:
@@ -653,16 +1079,16 @@ class Data(GpgmeWrapper):
             return b''.join(chunks)
 
 def pubkey_algo_name(algo):
-    return pygpgme.gpgme_pubkey_algo_name(algo)
+    return gpgme.gpgme_pubkey_algo_name(algo)
 
 def hash_algo_name(algo):
-    return pygpgme.gpgme_hash_algo_name(algo)
+    return gpgme.gpgme_hash_algo_name(algo)
 
 def get_protocol_name(proto):
-    return pygpgme.gpgme_get_protocol_name(proto)
+    return gpgme.gpgme_get_protocol_name(proto)
 
 def check_version(version=None):
-    return pygpgme.gpgme_check_version(version)
+    return gpgme.gpgme_check_version(version)
 
 # check_version also makes sure that several subsystems are properly
 # initialized, and it must be run at least once before invoking any
@@ -672,19 +1098,19 @@ check_version()
 
 def engine_check_version (proto):
     try:
-        errorcheck(pygpgme.gpgme_engine_check_version(proto))
+        errorcheck(gpgme.gpgme_engine_check_version(proto))
         return True
     except errors.GPGMEError:
         return False
 
 def get_engine_info():
-    ptr = pygpgme.new_gpgme_engine_info_t_p()
+    ptr = gpgme.new_gpgme_engine_info_t_p()
     try:
-        errorcheck(pygpgme.gpgme_get_engine_info(ptr))
-        info = pygpgme.gpgme_engine_info_t_p_value(ptr)
+        errorcheck(gpgme.gpgme_get_engine_info(ptr))
+        info = gpgme.gpgme_engine_info_t_p_value(ptr)
     except errors.GPGMEError:
         info = None
-    pygpgme.delete_gpgme_engine_info_t_p(ptr)
+    gpgme.delete_gpgme_engine_info_t_p(ptr)
     return info
 
 def set_engine_info(proto, file_name, home_dir=None):
@@ -693,11 +1119,11 @@ def set_engine_info(proto, file_name, home_dir=None):
     the executable program implementing this protocol. 'home_dir' is the
     directory name of the configuration directory (engine's default is
     used if omitted)."""
-    errorcheck(pygpgme.gpgme_set_engine_info(proto, file_name, home_dir))
+    errorcheck(gpgme.gpgme_set_engine_info(proto, file_name, home_dir))
 
 def set_locale(category, value):
     """Sets the default locale used by contexts"""
-    errorcheck(pygpgme.gpgme_set_locale(None, category, value))
+    errorcheck(gpgme.gpgme_set_locale(None, category, value))
 
 def wait(hang):
     """Wait for asynchronous call on any Context  to finish.
@@ -708,10 +1134,10 @@ def wait(hang):
         context - context which caused this call to return.
 
     Please read the GPGME manual of more information."""
-    ptr = pygpgme.new_gpgme_error_t_p()
-    context = pygpgme.gpgme_wait(None, ptr, hang)
-    status = pygpgme.gpgme_error_t_p_value(ptr)
-    pygpgme.delete_gpgme_error_t_p(ptr)
+    ptr = gpgme.new_gpgme_error_t_p()
+    context = gpgme.gpgme_wait(None, ptr, hang)
+    status = gpgme.gpgme_error_t_p_value(ptr)
+    gpgme.delete_gpgme_error_t_p(ptr)
     if context == None:
         errorcheck(status)
     else: