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 <justus@g10code.com>
This commit is contained in:
Justus Winter 2016-09-16 14:56:29 +02:00
parent 5259f9de46
commit a458e7fe20
8 changed files with 296 additions and 38 deletions

View File

@ -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)

View File

@ -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;
}

View File

@ -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

View File

@ -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,
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,

View File

@ -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"

View File

@ -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"

View File

@ -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

View File

@ -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)