diff options
| author | Justus Winter <[email protected]> | 2016-09-16 12:56:29 +0000 | 
|---|---|---|
| committer | Justus Winter <[email protected]> | 2016-09-16 12:56:29 +0000 | 
| commit | a458e7fe2006d92bd5a838e2747fb66bbac4b1b8 (patch) | |
| tree | 7460cc8f3ba21fec51a0a706f17747887ef37d96 | |
| parent | core: Fix typos. (diff) | |
| download | gpgme-a458e7fe2006d92bd5a838e2747fb66bbac4b1b8.tar.gz gpgme-a458e7fe2006d92bd5a838e2747fb66bbac4b1b8.zip | |
python: Adapt to 'gpgme_op_interact'.
* 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 <[email protected]>
| -rw-r--r-- | lang/python/examples/inter-edit.py | 15 | ||||
| -rw-r--r-- | lang/python/gpgme.i | 10 | ||||
| -rw-r--r-- | lang/python/helpers.c | 23 | ||||
| -rw-r--r-- | lang/python/private.h | 5 | ||||
| -rw-r--r-- | lang/python/pyme/constants/__init__.py | 105 | ||||
| -rw-r--r-- | lang/python/pyme/constants/status.py | 106 | ||||
| -rw-r--r-- | lang/python/pyme/core.py | 59 | ||||
| -rwxr-xr-x | lang/python/tests/t-edit.py | 11 | 
8 files changed, 296 insertions, 38 deletions
| diff --git a/lang/python/examples/inter-edit.py b/lang/python/examples/inter-edit.py index 459df114..39d6f176 100644 --- a/lang/python/examples/inter-edit.py +++ b/lang/python/examples/inter-edit.py @@ -23,13 +23,6 @@ del absolute_import, print_function, unicode_literals  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]) @@ -46,11 +39,11 @@ with pyme.Context() as c:      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( -            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 @@ -60,4 +53,4 @@ with pyme.Context() as c:          except EOFError:              return "quit" -    c.op_edit(key, edit_fnc, None, sys.stdout) +    c.interact(key, edit_fnc, sink=sys.stdout) diff --git a/lang/python/gpgme.i b/lang/python/gpgme.i index 458ae7f1..84addae2 100644 --- a/lang/python/gpgme.i +++ b/lang/python/gpgme.i @@ -476,15 +476,15 @@ -// 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)) -    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, -                        "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;  } diff --git a/lang/python/helpers.c b/lang/python/helpers.c index bc8aed40..bb2128c9 100644 --- a/lang/python/helpers.c +++ b/lang/python/helpers.c @@ -656,11 +656,16 @@ pyme_set_status_cb(PyObject *self, PyObject *cb) {    Py_INCREF(Py_None);    return Py_None;  } + -/* 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 *py_keyword;    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);    } -  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 */ @@ -726,7 +739,9 @@ gpgme_error_t _pyme_edit_cb(void *opaque, gpgme_status_code_t status,    Py_XDECREF(retval);    return err_status;  } + +  /* Data callbacks.  */  /* Read up to SIZE bytes into buffer BUFFER from the data object with diff --git a/lang/python/private.h b/lang/python/private.h index cb4d2f80..3a903c18 100644 --- a/lang/python/private.h +++ b/lang/python/private.h @@ -34,9 +34,8 @@ PyObject *_pyme_obj2gpgme_data_t(PyObject *input, int argnum,  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, diff --git a/lang/python/pyme/constants/__init__.py b/lang/python/pyme/constants/__init__.py index 96465de5..96d89e47 100644 --- a/lang/python/pyme/constants/__init__.py +++ b/lang/python/pyme/constants/__init__.py @@ -7,3 +7,108 @@ util.process_constants('GPGME_', globals())  __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" diff --git a/lang/python/pyme/constants/status.py b/lang/python/pyme/constants/status.py index ee522591..a04d9aae 100644 --- a/lang/python/pyme/constants/status.py +++ b/lang/python/pyme/constants/status.py @@ -18,5 +18,107 @@  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" diff --git a/lang/python/pyme/core.py b/lang/python/pyme/core.py index 55e86872..88a086b1 100644 --- a/lang/python/pyme/core.py +++ b/lang/python/pyme/core.py @@ -29,6 +29,7 @@ del absolute_import, print_function, unicode_literals  import re  import os +import warnings  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 +    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""" @@ -793,18 +827,21 @@ class Context(GpgmeWrapper):          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 diff --git a/lang/python/tests/t-edit.py b/lang/python/tests/t-edit.py index 9ba187d5..18bcb94e 100755 --- a/lang/python/tests/t-edit.py +++ b/lang/python/tests/t-edit.py @@ -33,7 +33,7 @@ class KeyEditor(object):          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 @@ -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) -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) | 
