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 sys
import pyme 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: if len(sys.argv) != 2:
sys.exit("Usage: %s <Gpg key pattern>\n" % sys.argv[0]) sys.exit("Usage: %s <Gpg key pattern>\n" % sys.argv[0])
@ -46,11 +39,11 @@ with pyme.Context() as c:
key = keys[0] key = keys[0]
print("Editing key {} ({}):".format(key.uids[0].uid, key.subkeys[0].fpr)) 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 # no prompt
print() print()
return None return None
@ -60,4 +53,4 @@ with pyme.Context() as c:
except EOFError: except EOFError:
return "quit" 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 /* Include mapper for interact callbacks. */
%typemap(in) (gpgme_edit_cb_t fnc, void *fnc_value) { %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) if (PyTuple_Size($input) != 2 && PyTuple_Size($input) != 3)
return PyErr_Format(PyExc_TypeError, 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;
} }

View File

@ -656,11 +656,16 @@ pyme_set_status_cb(PyObject *self, PyObject *cb) {
Py_INCREF(Py_None); Py_INCREF(Py_None);
return Py_None; return Py_None;
} }
/* Edit callbacks. */
gpgme_error_t _pyme_edit_cb(void *opaque, gpgme_status_code_t status, /* Interact callbacks. */
const char *args, int fd) { 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; PyObject *pyopaque = (PyObject *) opaque;
gpgme_error_t err_status = 0; gpgme_error_t err_status = 0;
PyObject *self = NULL; 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)); PyTuple_SetItem(pyargs, 1, PyUnicode_FromString(args));
if (dataarg) { if (dataarg) {
Py_INCREF(dataarg); /* Because GetItem doesn't give a ref but SetItem taketh away */ 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); Py_XDECREF(retval);
return err_status; return err_status;
} }
/* Data callbacks. */ /* Data callbacks. */
/* Read up to SIZE bytes into buffer BUFFER from the data object with /* 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); 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); const char *args, int fd);
gpgme_error_t _pyme_assuan_data_cb (void *hook, gpgme_error_t _pyme_assuan_data_cb (void *hook,
const void *data, size_t datalen); const void *data, size_t datalen);
gpgme_error_t _pyme_assuan_inquire_cb (void *hook, 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', __all__ = ['data', 'event', 'import', 'keylist', 'md', 'pk',
'protocol', 'sig', 'sigsum', 'status', 'validity'] '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 from __future__ import absolute_import, print_function, unicode_literals
del absolute_import, print_function, unicode_literals del absolute_import, print_function, unicode_literals
from pyme import util # GPGME 1.7 replaced gpgme_op_edit with gpgme_op_interact. We
util.process_constants('GPGME_STATUS_', globals()) # 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 re
import os import os
import warnings
import weakref import weakref
from . import gpgme from . import gpgme
from .errors import errorcheck, GPGMEError 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 @property
def signers(self): def signers(self):
"""Keys used for signing""" """Keys used for signing"""
@ -793,18 +827,21 @@ class Context(GpgmeWrapper):
errorcheck(status) errorcheck(status)
def op_edit(self, key, func, fnc_value, out): def op_edit(self, key, func, fnc_value, out):
"""Start key editing using supplied callback function""" """Start key editing using supplied callback function
if key == None:
raise ValueError("op_edit: First argument cannot be None") Note: This interface is deprecated and will be removed with
if fnc_value: GPGME 1.8. Please use .interact instead. Furthermore, we
opaquedata = (weakref.ref(self), func, fnc_value) implement this using gpgme_op_interact, so callbacks will get
else: called with string keywords instead of numeric status
opaquedata = (weakref.ref(self), func) 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): class Data(GpgmeWrapper):
"""Data buffer """Data buffer

View File

@ -33,7 +33,7 @@ class KeyEditor(object):
self.done = False self.done = False
self.verbose = int(os.environ.get('verbose', 0)) > 1 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": if args == "keyedit.prompt":
result = self.steps[self.step] result = self.steps[self.step]
self.step += 1 self.step += 1
@ -57,8 +57,15 @@ c = core.Context()
c.set_pinentry_mode(constants.PINENTRY_MODE_LOOPBACK) c.set_pinentry_mode(constants.PINENTRY_MODE_LOOPBACK)
c.set_passphrase_cb(lambda *args: "abc") c.set_passphrase_cb(lambda *args: "abc")
c.set_armor(True) 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() editor = KeyEditor()
c.op_edit(c.get_key("A0FF4590BB6122EDEF6E3C542D727CC768697734", False), c.op_edit(c.get_key("A0FF4590BB6122EDEF6E3C542D727CC768697734", False),
editor.edit_fnc, sink, sink) editor.edit_fnc, sink, sink)