python: Adapt to 'gpgme_op_interact'.
authorJustus Winter <justus@g10code.com>
Fri, 16 Sep 2016 12:56:29 +0000 (14:56 +0200)
committerJustus Winter <justus@g10code.com>
Fri, 16 Sep 2016 12:56:29 +0000 (14:56 +0200)
* lang/python/examples/inter-edit.py: Update example.
* lang/python/gpgme.i (gpgme_edit_cb_t): Turn into
'gpgme_interact_cb_t'.
* lang/python/helpers.c (_pyme_edit_cb): Turn into
'_pyme_interact_cb_t'.
* lang/python/private.h (_pyme_edit_cb): Likewise.
* lang/python/pyme/constants/__init__.py: Replace numeric status codes
with the keywords.
* lang/python/pyme/constants/status.py: Likewise.
* lang/python/pyme/core.py (Context.interact): New method.
(Context.op_edit): Deprecate, update docstring, implement using
Context.interact.
* lang/python/tests/t-edit.py: Test both interfaces.

Signed-off-by: Justus Winter <justus@g10code.com>
lang/python/examples/inter-edit.py
lang/python/gpgme.i
lang/python/helpers.c
lang/python/private.h
lang/python/pyme/constants/__init__.py
lang/python/pyme/constants/status.py
lang/python/pyme/core.py
lang/python/tests/t-edit.py

index 459df11..39d6f17 100644 (file)
@@ -23,13 +23,6 @@ del absolute_import, print_function, unicode_literals
 
 import sys
 import pyme
 
 import sys
 import pyme
-import pyme.constants.status
-
-# Get names for the status codes
-status2str = {}
-for name in dir(pyme.constants.status):
-    if not name.startswith('__') and name != "util":
-        status2str[getattr(pyme.constants.status, name)] = name
 
 if len(sys.argv) != 2:
     sys.exit("Usage: %s <Gpg key pattern>\n" % sys.argv[0])
 
 if len(sys.argv) != 2:
     sys.exit("Usage: %s <Gpg key pattern>\n" % sys.argv[0])
@@ -46,11 +39,11 @@ with pyme.Context() as c:
     key = keys[0]
     print("Editing key {} ({}):".format(key.uids[0].uid, key.subkeys[0].fpr))
 
     key = keys[0]
     print("Editing key {} ({}):".format(key.uids[0].uid, key.subkeys[0].fpr))
 
-    def edit_fnc(status, args):
+    def edit_fnc(keyword, args):
         print("Status: {} ({}), args: {} > ".format(
         print("Status: {} ({}), args: {} > ".format(
-            status2str[status], status, args), end='', flush=True)
+            keyword, status, args), end='', flush=True)
 
 
-        if not 'GET' in status2str[status]:
+        if not 'GET' in keyword:
             # no prompt
             print()
             return None
             # no prompt
             print()
             return None
@@ -60,4 +53,4 @@ with pyme.Context() as c:
         except EOFError:
             return "quit"
 
         except EOFError:
             return "quit"
 
-    c.op_edit(key, edit_fnc, None, sys.stdout)
+    c.interact(key, edit_fnc, sink=sys.stdout)
index 458ae7f..84addae 100644 (file)
 
 \f
 
 
 \f
 
-// Include mapper for edit callbacks
-%typemap(in) (gpgme_edit_cb_t fnc, void *fnc_value) {
+/* Include mapper for interact callbacks.  */
+%typemap(in) (gpgme_interact_cb_t fnc, void *fnc_value) {
   if (! PyTuple_Check($input))
   if (! PyTuple_Check($input))
-    return PyErr_Format(PyExc_TypeError, "edit callback must be a tuple");
+    return PyErr_Format(PyExc_TypeError, "interact callback must be a tuple");
   if (PyTuple_Size($input) != 2 && PyTuple_Size($input) != 3)
     return PyErr_Format(PyExc_TypeError,
   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");
+                        "interact callback must be a tuple of size 2 or 3");
 
 
-  $1 = (gpgme_edit_cb_t) _pyme_edit_cb;
+  $1 = (gpgme_interact_cb_t) _pyme_interact_cb;
   $2 = $input;
 }
 
   $2 = $input;
 }
 
index bc8aed4..bb2128c 100644 (file)
@@ -656,11 +656,16 @@ pyme_set_status_cb(PyObject *self, PyObject *cb) {
   Py_INCREF(Py_None);
   return Py_None;
 }
   Py_INCREF(Py_None);
   return Py_None;
 }
+
 \f
 \f
-/* Edit callbacks.  */
-gpgme_error_t _pyme_edit_cb(void *opaque, gpgme_status_code_t status,
-                      const char *args, int fd) {
+
+/* Interact callbacks.  */
+gpgme_error_t
+_pyme_interact_cb(void *opaque, const char *keyword,
+                  const char *args, int fd)
+{
   PyObject *func = NULL, *dataarg = NULL, *pyargs = NULL, *retval = NULL;
   PyObject *func = NULL, *dataarg = NULL, *pyargs = NULL, *retval = NULL;
+  PyObject *py_keyword;
   PyObject *pyopaque = (PyObject *) opaque;
   gpgme_error_t err_status = 0;
   PyObject *self = NULL;
   PyObject *pyopaque = (PyObject *) opaque;
   gpgme_error_t err_status = 0;
   PyObject *self = NULL;
@@ -678,7 +683,15 @@ gpgme_error_t _pyme_edit_cb(void *opaque, gpgme_status_code_t status,
     pyargs = PyTuple_New(2);
   }
 
     pyargs = PyTuple_New(2);
   }
 
-  PyTuple_SetItem(pyargs, 0, PyLong_FromLong((long) status));
+  if (keyword)
+    py_keyword = PyUnicode_FromString(keyword);
+  else
+    {
+      Py_INCREF(Py_None);
+      py_keyword = Py_None;
+    }
+
+  PyTuple_SetItem(pyargs, 0, py_keyword);
   PyTuple_SetItem(pyargs, 1, PyUnicode_FromString(args));
   if (dataarg) {
     Py_INCREF(dataarg);                /* Because GetItem doesn't give a ref but SetItem taketh away */
   PyTuple_SetItem(pyargs, 1, PyUnicode_FromString(args));
   if (dataarg) {
     Py_INCREF(dataarg);                /* Because GetItem doesn't give a ref but SetItem taketh away */
@@ -726,7 +739,9 @@ gpgme_error_t _pyme_edit_cb(void *opaque, gpgme_status_code_t status,
   Py_XDECREF(retval);
   return err_status;
 }
   Py_XDECREF(retval);
   return err_status;
 }
+
 \f
 \f
+
 /* Data callbacks.  */
 
 /* Read up to SIZE bytes into buffer BUFFER from the data object with
 /* Data callbacks.  */
 
 /* Read up to SIZE bytes into buffer BUFFER from the data object with
index cb4d2f8..3a903c1 100644 (file)
@@ -34,9 +34,8 @@ PyObject *_pyme_obj2gpgme_data_t(PyObject *input, int argnum,
 
 PyObject *_pyme_wrap_result(PyObject *fragile, const char *classname);
 
 
 PyObject *_pyme_wrap_result(PyObject *fragile, const char *classname);
 
-gpgme_error_t _pyme_edit_cb(void *opaque, gpgme_status_code_t status,
-                           const char *args, int fd);
-
+gpgme_error_t _pyme_interact_cb(void *opaque, const char *keyword,
+                               const char *args, int fd);
 gpgme_error_t _pyme_assuan_data_cb (void *hook,
                                    const void *data, size_t datalen);
 gpgme_error_t _pyme_assuan_inquire_cb (void *hook,
 gpgme_error_t _pyme_assuan_data_cb (void *hook,
                                    const void *data, size_t datalen);
 gpgme_error_t _pyme_assuan_inquire_cb (void *hook,
index 96465de..96d89e4 100644 (file)
@@ -7,3 +7,108 @@ util.process_constants('GPGME_', globals())
 
 __all__ = ['data', 'event', 'import', 'keylist', 'md', 'pk',
            'protocol', 'sig', 'sigsum', 'status', 'validity']
 
 __all__ = ['data', 'event', 'import', 'keylist', 'md', 'pk',
            'protocol', 'sig', 'sigsum', 'status', 'validity']
+
+# GPGME 1.7 replaced gpgme_op_edit with gpgme_op_interact.  We
+# implement pyme.Context.op_edit using gpgme_op_interact, so the
+# callbacks will be called with string keywords instead of numeric
+# status messages.  Code that is using these constants will continue
+# to work.
+
+STATUS_ABORT = "ABORT"
+STATUS_ALREADY_SIGNED = "ALREADY_SIGNED"
+STATUS_ATTRIBUTE = "ATTRIBUTE"
+STATUS_BACKUP_KEY_CREATED = "BACKUP_KEY_CREATED"
+STATUS_BAD_PASSPHRASE = "BAD_PASSPHRASE"
+STATUS_BADARMOR = "BADARMOR"
+STATUS_BADMDC = "BADMDC"
+STATUS_BADSIG = "BADSIG"
+STATUS_BEGIN_DECRYPTION = "BEGIN_DECRYPTION"
+STATUS_BEGIN_ENCRYPTION = "BEGIN_ENCRYPTION"
+STATUS_BEGIN_SIGNING = "BEGIN_SIGNING"
+STATUS_BEGIN_STREAM = "BEGIN_STREAM"
+STATUS_CARDCTRL = "CARDCTRL"
+STATUS_DECRYPTION_FAILED = "DECRYPTION_FAILED"
+STATUS_DECRYPTION_INFO = "DECRYPTION_INFO"
+STATUS_DECRYPTION_OKAY = "DECRYPTION_OKAY"
+STATUS_DELETE_PROBLEM = "DELETE_PROBLEM"
+STATUS_ENC_TO = "ENC_TO"
+STATUS_END_DECRYPTION = "END_DECRYPTION"
+STATUS_END_ENCRYPTION = "END_ENCRYPTION"
+STATUS_END_STREAM = "END_STREAM"
+STATUS_ENTER = "ENTER"
+STATUS_ERRMDC = "ERRMDC"
+STATUS_ERROR = "ERROR"
+STATUS_ERRSIG = "ERRSIG"
+STATUS_EXPKEYSIG = "EXPKEYSIG"
+STATUS_EXPSIG = "EXPSIG"
+STATUS_FAILURE = "FAILURE"
+STATUS_FILE_DONE = "FILE_DONE"
+STATUS_FILE_ERROR = "FILE_ERROR"
+STATUS_FILE_START = "FILE_START"
+STATUS_GET_BOOL = "GET_BOOL"
+STATUS_GET_HIDDEN = "GET_HIDDEN"
+STATUS_GET_LINE = "GET_LINE"
+STATUS_GOOD_PASSPHRASE = "GOOD_PASSPHRASE"
+STATUS_GOODMDC = "GOODMDC"
+STATUS_GOODSIG = "GOODSIG"
+STATUS_GOT_IT = "GOT_IT"
+STATUS_IMPORT_OK = "IMPORT_OK"
+STATUS_IMPORT_PROBLEM = "IMPORT_PROBLEM"
+STATUS_IMPORT_RES = "IMPORT_RES"
+STATUS_IMPORTED = "IMPORTED"
+STATUS_INQUIRE_MAXLEN = "INQUIRE_MAXLEN"
+STATUS_INV_RECP = "INV_RECP"
+STATUS_INV_SGNR = "INV_SGNR"
+STATUS_KEY_CONSIDERED = "KEY_CONSIDERED"
+STATUS_KEY_CREATED = "KEY_CREATED"
+STATUS_KEY_NOT_CREATED = "KEY_NOT_CREATED"
+STATUS_KEYEXPIRED = "KEYEXPIRED"
+STATUS_KEYREVOKED = "KEYREVOKED"
+STATUS_LEAVE = "LEAVE"
+STATUS_MISSING_PASSPHRASE = "MISSING_PASSPHRASE"
+STATUS_MOUNTPOINT = "MOUNTPOINT"
+STATUS_NEED_PASSPHRASE = "NEED_PASSPHRASE"
+STATUS_NEED_PASSPHRASE_PIN = "NEED_PASSPHRASE_PIN"
+STATUS_NEED_PASSPHRASE_SYM = "NEED_PASSPHRASE_SYM"
+STATUS_NEWSIG = "NEWSIG"
+STATUS_NO_PUBKEY = "NO_PUBKEY"
+STATUS_NO_RECP = "NO_RECP"
+STATUS_NO_SECKEY = "NO_SECKEY"
+STATUS_NO_SGNR = "NO_SGNR"
+STATUS_NODATA = "NODATA"
+STATUS_NOTATION_DATA = "NOTATION_DATA"
+STATUS_NOTATION_FLAGS = "NOTATION_FLAGS"
+STATUS_NOTATION_NAME = "NOTATION_NAME"
+STATUS_PINENTRY_LAUNCHED = "PINENTRY_LAUNCHED"
+STATUS_PKA_TRUST_BAD = "PKA_TRUST_BAD"
+STATUS_PKA_TRUST_GOOD = "PKA_TRUST_GOOD"
+STATUS_PLAINTEXT = "PLAINTEXT"
+STATUS_PLAINTEXT_LENGTH = "PLAINTEXT_LENGTH"
+STATUS_POLICY_URL = "POLICY_URL"
+STATUS_PROGRESS = "PROGRESS"
+STATUS_REVKEYSIG = "REVKEYSIG"
+STATUS_RSA_OR_IDEA = "RSA_OR_IDEA"
+STATUS_SC_OP_FAILURE = "SC_OP_FAILURE"
+STATUS_SC_OP_SUCCESS = "SC_OP_SUCCESS"
+STATUS_SESSION_KEY = "SESSION_KEY"
+STATUS_SHM_GET = "SHM_GET"
+STATUS_SHM_GET_BOOL = "SHM_GET_BOOL"
+STATUS_SHM_GET_HIDDEN = "SHM_GET_HIDDEN"
+STATUS_SHM_INFO = "SHM_INFO"
+STATUS_SIG_CREATED = "SIG_CREATED"
+STATUS_SIG_ID = "SIG_ID"
+STATUS_SIG_SUBPACKET = "SIG_SUBPACKET"
+STATUS_SIGEXPIRED = "SIGEXPIRED"
+STATUS_SUCCESS = "SUCCESS"
+STATUS_TOFU_STATS = "TOFU_STATS"
+STATUS_TOFU_STATS_LONG = "TOFU_STATS_LONG"
+STATUS_TOFU_USER = "TOFU_USER"
+STATUS_TRUNCATED = "TRUNCATED"
+STATUS_TRUST_FULLY = "TRUST_FULLY"
+STATUS_TRUST_MARGINAL = "TRUST_MARGINAL"
+STATUS_TRUST_NEVER = "TRUST_NEVER"
+STATUS_TRUST_ULTIMATE = "TRUST_ULTIMATE"
+STATUS_TRUST_UNDEFINED = "TRUST_UNDEFINED"
+STATUS_UNEXPECTED = "UNEXPECTED"
+STATUS_USERID_HINT = "USERID_HINT"
+STATUS_VALIDSIG = "VALIDSIG"
index ee52259..a04d9aa 100644 (file)
 from __future__ import absolute_import, print_function, unicode_literals
 del absolute_import, print_function, unicode_literals
 
 from __future__ import absolute_import, print_function, unicode_literals
 del absolute_import, print_function, unicode_literals
 
-from pyme import util
-util.process_constants('GPGME_STATUS_', globals())
+# GPGME 1.7 replaced gpgme_op_edit with gpgme_op_interact.  We
+# implement pyme.Context.op_edit using gpgme_op_interact, so the
+# callbacks will be called with string keywords instead of numeric
+# status messages.  Code that is using these constants will continue
+# to work.
+
+ABORT = "ABORT"
+ALREADY_SIGNED = "ALREADY_SIGNED"
+ATTRIBUTE = "ATTRIBUTE"
+BACKUP_KEY_CREATED = "BACKUP_KEY_CREATED"
+BAD_PASSPHRASE = "BAD_PASSPHRASE"
+BADARMOR = "BADARMOR"
+BADMDC = "BADMDC"
+BADSIG = "BADSIG"
+BEGIN_DECRYPTION = "BEGIN_DECRYPTION"
+BEGIN_ENCRYPTION = "BEGIN_ENCRYPTION"
+BEGIN_SIGNING = "BEGIN_SIGNING"
+BEGIN_STREAM = "BEGIN_STREAM"
+CARDCTRL = "CARDCTRL"
+DECRYPTION_FAILED = "DECRYPTION_FAILED"
+DECRYPTION_INFO = "DECRYPTION_INFO"
+DECRYPTION_OKAY = "DECRYPTION_OKAY"
+DELETE_PROBLEM = "DELETE_PROBLEM"
+ENC_TO = "ENC_TO"
+END_DECRYPTION = "END_DECRYPTION"
+END_ENCRYPTION = "END_ENCRYPTION"
+END_STREAM = "END_STREAM"
+ENTER = "ENTER"
+ERRMDC = "ERRMDC"
+ERROR = "ERROR"
+ERRSIG = "ERRSIG"
+EXPKEYSIG = "EXPKEYSIG"
+EXPSIG = "EXPSIG"
+FAILURE = "FAILURE"
+FILE_DONE = "FILE_DONE"
+FILE_ERROR = "FILE_ERROR"
+FILE_START = "FILE_START"
+GET_BOOL = "GET_BOOL"
+GET_HIDDEN = "GET_HIDDEN"
+GET_LINE = "GET_LINE"
+GOOD_PASSPHRASE = "GOOD_PASSPHRASE"
+GOODMDC = "GOODMDC"
+GOODSIG = "GOODSIG"
+GOT_IT = "GOT_IT"
+IMPORT_OK = "IMPORT_OK"
+IMPORT_PROBLEM = "IMPORT_PROBLEM"
+IMPORT_RES = "IMPORT_RES"
+IMPORTED = "IMPORTED"
+INQUIRE_MAXLEN = "INQUIRE_MAXLEN"
+INV_RECP = "INV_RECP"
+INV_SGNR = "INV_SGNR"
+KEY_CONSIDERED = "KEY_CONSIDERED"
+KEY_CREATED = "KEY_CREATED"
+KEY_NOT_CREATED = "KEY_NOT_CREATED"
+KEYEXPIRED = "KEYEXPIRED"
+KEYREVOKED = "KEYREVOKED"
+LEAVE = "LEAVE"
+MISSING_PASSPHRASE = "MISSING_PASSPHRASE"
+MOUNTPOINT = "MOUNTPOINT"
+NEED_PASSPHRASE = "NEED_PASSPHRASE"
+NEED_PASSPHRASE_PIN = "NEED_PASSPHRASE_PIN"
+NEED_PASSPHRASE_SYM = "NEED_PASSPHRASE_SYM"
+NEWSIG = "NEWSIG"
+NO_PUBKEY = "NO_PUBKEY"
+NO_RECP = "NO_RECP"
+NO_SECKEY = "NO_SECKEY"
+NO_SGNR = "NO_SGNR"
+NODATA = "NODATA"
+NOTATION_DATA = "NOTATION_DATA"
+NOTATION_FLAGS = "NOTATION_FLAGS"
+NOTATION_NAME = "NOTATION_NAME"
+PINENTRY_LAUNCHED = "PINENTRY_LAUNCHED"
+PKA_TRUST_BAD = "PKA_TRUST_BAD"
+PKA_TRUST_GOOD = "PKA_TRUST_GOOD"
+PLAINTEXT = "PLAINTEXT"
+PLAINTEXT_LENGTH = "PLAINTEXT_LENGTH"
+POLICY_URL = "POLICY_URL"
+PROGRESS = "PROGRESS"
+REVKEYSIG = "REVKEYSIG"
+RSA_OR_IDEA = "RSA_OR_IDEA"
+SC_OP_FAILURE = "SC_OP_FAILURE"
+SC_OP_SUCCESS = "SC_OP_SUCCESS"
+SESSION_KEY = "SESSION_KEY"
+SHM_GET = "SHM_GET"
+SHM_GET_BOOL = "SHM_GET_BOOL"
+SHM_GET_HIDDEN = "SHM_GET_HIDDEN"
+SHM_INFO = "SHM_INFO"
+SIG_CREATED = "SIG_CREATED"
+SIG_ID = "SIG_ID"
+SIG_SUBPACKET = "SIG_SUBPACKET"
+SIGEXPIRED = "SIGEXPIRED"
+SUCCESS = "SUCCESS"
+TOFU_STATS = "TOFU_STATS"
+TOFU_STATS_LONG = "TOFU_STATS_LONG"
+TOFU_USER = "TOFU_USER"
+TRUNCATED = "TRUNCATED"
+TRUST_FULLY = "TRUST_FULLY"
+TRUST_MARGINAL = "TRUST_MARGINAL"
+TRUST_NEVER = "TRUST_NEVER"
+TRUST_ULTIMATE = "TRUST_ULTIMATE"
+TRUST_UNDEFINED = "TRUST_UNDEFINED"
+UNEXPECTED = "UNEXPECTED"
+USERID_HINT = "USERID_HINT"
+VALIDSIG = "VALIDSIG"
index 55e8687..88a086b 100644 (file)
@@ -29,6 +29,7 @@ del absolute_import, print_function, unicode_literals
 
 import re
 import os
 
 import re
 import os
+import warnings
 import weakref
 from . import gpgme
 from .errors import errorcheck, GPGMEError
 import weakref
 from . import gpgme
 from .errors import errorcheck, GPGMEError
@@ -536,6 +537,39 @@ class Context(GpgmeWrapper):
 
         return GPGMEError(status) if status != 0 else None
 
 
         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"""
     @property
     def signers(self):
         """Keys used for signing"""
@@ -793,18 +827,21 @@ class Context(GpgmeWrapper):
         errorcheck(status)
 
     def op_edit(self, key, func, fnc_value, out):
         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 = gpgme.gpgme_op_edit(self.wrapped, key, opaquedata, out)
-        if self._callback_excinfo:
-            gpgme.pyme_raise_callback_exception(self)
-        errorcheck(result)
 
 class Data(GpgmeWrapper):
     """Data buffer
 
 class Data(GpgmeWrapper):
     """Data buffer
index 9ba187d..18bcb94 100755 (executable)
@@ -33,7 +33,7 @@ class KeyEditor(object):
         self.done = False
         self.verbose = int(os.environ.get('verbose', 0)) > 1
 
         self.done = False
         self.verbose = int(os.environ.get('verbose', 0)) > 1
 
-    def edit_fnc(self, status, args, out):
+    def edit_fnc(self, status, args, out=None):
         if args == "keyedit.prompt":
             result = self.steps[self.step]
             self.step += 1
         if args == "keyedit.prompt":
             result = self.steps[self.step]
             self.step += 1
@@ -57,8 +57,15 @@ c = core.Context()
 c.set_pinentry_mode(constants.PINENTRY_MODE_LOOPBACK)
 c.set_passphrase_cb(lambda *args: "abc")
 c.set_armor(True)
 c.set_pinentry_mode(constants.PINENTRY_MODE_LOOPBACK)
 c.set_passphrase_cb(lambda *args: "abc")
 c.set_armor(True)
-sink = core.Data()
 
 
+# The deprecated interface.
+editor = KeyEditor()
+c.interact(c.get_key("A0FF4590BB6122EDEF6E3C542D727CC768697734", False),
+           editor.edit_fnc)
+assert editor.done
+
+# The deprecated interface.
+sink = core.Data()
 editor = KeyEditor()
 c.op_edit(c.get_key("A0FF4590BB6122EDEF6E3C542D727CC768697734", False),
           editor.edit_fnc, sink, sink)
 editor = KeyEditor()
 c.op_edit(c.get_key("A0FF4590BB6122EDEF6E3C542D727CC768697734", False),
           editor.edit_fnc, sink, sink)