From a3d111be12144d9dea80165d84cd8168714d3d54 Mon Sep 17 00:00:00 2001 From: Lance Stout Date: Wed, 23 Mar 2011 23:00:41 -0400 Subject: Added new XEP-0050 implementation. Backward incompatibility alert! Please see examples/adhoc_provider.py for how to use the new plugin implementation, or the test examples in the files tests/test_stream_xep_0050.py and tests/test_stanza_xep_0050.py. Major changes: - May now have zero-step commands. Useful if a command is intended to be a dynamic status report that doesn't require any user input. - May use payloads other than data forms, such as a completely custom stanza type. - May include multiple payload items, such as multiple data forms, or a form and a custom stanza type. - Includes a command user API for calling adhoc commands on remote agents and managing the workflow. - Added support for note elements. Todo: - Add prev action support. You may use register_plugin('old_0050') to continue using the previous XEP-0050 implementation. --- sleekxmpp/plugins/xep_0050/stanza.py | 185 +++++++++++++++++++++++++++++++++++ 1 file changed, 185 insertions(+) create mode 100644 sleekxmpp/plugins/xep_0050/stanza.py (limited to 'sleekxmpp/plugins/xep_0050/stanza.py') diff --git a/sleekxmpp/plugins/xep_0050/stanza.py b/sleekxmpp/plugins/xep_0050/stanza.py new file mode 100644 index 00000000..31a4a5d5 --- /dev/null +++ b/sleekxmpp/plugins/xep_0050/stanza.py @@ -0,0 +1,185 @@ +""" + SleekXMPP: The Sleek XMPP Library + Copyright (C) 2011 Nathanael C. Fritz, Lance J.T. Stout + This file is part of SleekXMPP. + + See the file LICENSE for copying permission. +""" + +from sleekxmpp.xmlstream import ElementBase, ET + + +class Command(ElementBase): + + """ + XMPP's Adhoc Commands provides a generic workflow mechanism for + interacting with applications. The result is similar to menu selections + and multi-step dialogs in normal desktop applications. Clients do not + need to know in advance what commands are provided by any particular + application or agent. While adhoc commands provide similar functionality + to Jabber-RPC, adhoc commands are used primarily for human interaction. + + Also see + + Example command stanzas: + + + + + + + + + + Information! + + + + + + + Stanza Interface: + action -- The action to perform. + actions -- The set of allowable next actions. + node -- The node associated with the command. + notes -- A list of tuples for informative notes. + sessionid -- A unique identifier for a command session. + status -- May be one of: canceled, completed, or executing. + + Attributes: + actions -- A set of allowed action values. + statuses -- A set of allowed status values. + next_actions -- A set of allowed next action names. + + Methods: + get_action -- Return the requested action. + get_actions -- Return the allowable next actions. + set_actions -- Set the allowable next actions. + del_actions -- Remove the current set of next actions. + get_notes -- Return a list of informative note data. + set_notes -- Set informative notes. + del_notes -- Remove any note data. + add_note -- Add a single note. + """ + + name = 'command' + namespace = 'http://jabber.org/protocol/commands' + plugin_attrib = 'command' + interfaces = set(('action', 'sessionid', 'node', + 'status', 'actions', 'notes')) + actions = set(('cancel', 'complete', 'execute', 'next', 'prev')) + statuses = set(('canceled', 'completed', 'executing')) + next_actions = set(('prev', 'next', 'complete')) + + def get_action(self): + """ + Return the value of the action attribute. + + If the Iq stanza's type is "set" then use a default + value of "execute". + """ + if self.parent()['type'] == 'set': + return self._get_attr('action', default='execute') + return self._get_attr('action') + + def set_actions(self, values): + """ + Assign the set of allowable next actions. + + Arguments: + values -- A list containing any combination of: + 'prev', 'next', and 'complete' + """ + self.del_actions() + if values: + self._set_sub_text('{%s}actions' % self.namespace, '', True) + actions = self.find('{%s}actions' % self.namespace) + for val in values: + if val in self.next_actions: + action = ET.Element('{%s}%s' % (self.namespace, val)) + actions.append(action) + + def get_actions(self): + """ + Return the set of allowable next actions. + """ + actions = [] + actions_xml = self.find('{%s}actions' % self.namespace) + if actions_xml is not None: + for action in self.next_actions: + action_xml = actions_xml.find('{%s}%s' % (self.namespace, + action)) + if action_xml is not None: + actions.append(action) + return actions + + def del_actions(self): + """ + Remove all allowable next actions. + """ + self._del_sub('{%s}actions' % self.namespace) + + def get_notes(self): + """ + Return a list of note information. + + Example: + [('info', 'Some informative data'), + ('warning', 'Use caution'), + ('error', 'The command ran, but had errors')] + """ + notes = [] + notes_xml = self.findall('{%s}note' % self.namespace) + for note in notes_xml: + notes.append((note.attrib.get('type', 'info'), + note.text)) + return notes + + def set_notes(self, notes): + """ + Add multiple notes to the command result. + + Each note is a tuple, with the first item being one of: + 'info', 'warning', or 'error', and the second item being + any human readable message. + + Example: + [('info', 'Some informative data'), + ('warning', 'Use caution'), + ('error', 'The command ran, but had errors')] + + + Arguments: + notes -- A list of tuples of note information. + """ + self.del_notes() + for note in notes: + self.add_note(note[1], note[0]) + + def del_notes(self): + """ + Remove all notes associated with the command result. + """ + notes_xml = self.findall('{%s}note' % self.namespace) + for note in notes_xml: + self.xml.remove(note) + + def add_note(self, msg='', ntype='info'): + """ + Add a single note annotation to the command. + + Arguments: + msg -- A human readable message. + ntype -- One of: 'info', 'warning', 'error' + """ + xml = ET.Element('{%s}note' % self.namespace) + xml.attrib['type'] = ntype + xml.text = msg + self.xml.append(xml) -- cgit v1.2.3