diff --git a/examples/custom_stanzas/custom_stanza_provider.py b/examples/custom_stanzas/custom_stanza_provider.py
index 16fc6c30..40a77204 100755
--- a/examples/custom_stanzas/custom_stanza_provider.py
+++ b/examples/custom_stanzas/custom_stanza_provider.py
@@ -37,8 +37,8 @@ else:
class ActionBot(sleekxmpp.ClientXMPP):
"""
- A simple SleekXMPP bot that provides a basic
- adhoc command.
+ A simple SleekXMPP bot that receives a custom stanza
+ from another client.
"""
def __init__(self, jid, password):
@@ -54,7 +54,12 @@ class ActionBot(sleekxmpp.ClientXMPP):
self.registerHandler(
Callback('Some custom iq',
StanzaPath('iq@type=set/action'),
- self._handleAction))
+ self._handle_action))
+
+ self.add_event_handler('custom_action',
+ self._handle_action_event,
+ threaded=True)
+
register_stanza_plugin(Iq, Action)
def start(self, event):
@@ -73,17 +78,34 @@ class ActionBot(sleekxmpp.ClientXMPP):
self.send_presence()
self.get_roster()
- def _handleAction(self, iq):
- if iq['action']['method'] == 'is_prime' and iq['action']['param'] == '2':
- print("got message: " + str(iq))
+ def _handle_action(self, iq):
+ """
+ Raise an event for the stanza so that it can be processed in its
+ own thread without blocking the main stanza processing loop.
+ """
+ self.event('custom_action', iq)
+
+ def _handle_action_event(self, iq):
+ """
+ Respond to the custom action event.
+
+ Since one of the actions is to disconnect, this
+ event handler needs to be run in threaded mode, by
+ using `threaded=True` in the `add_event_handler` call.
+ """
+ method = iq['action']['method']
+ param = iq['action']['param']
+
+ if method == 'is_prime' and param == '2':
+ print("got message: %s" % iq)
iq.reply()
iq['action']['status'] = 'done'
iq.send()
- elif iq['action']['method'] == 'bye':
- print("got message: " + str(iq))
+ elif method == 'bye':
+ print("got message: %s" % iq)
self.disconnect()
else:
- print("got message: " + str(iq))
+ print("got message: %s" % iq)
iq.reply()
iq['action']['status'] = 'error'
iq.send()
diff --git a/examples/custom_stanzas/custom_stanza_user.py b/examples/custom_stanzas/custom_stanza_user.py
index 0741b3a4..eabb45fe 100755
--- a/examples/custom_stanzas/custom_stanza_user.py
+++ b/examples/custom_stanzas/custom_stanza_user.py
@@ -15,8 +15,10 @@ import getpass
from optparse import OptionParser
import sleekxmpp
-
+from sleekxmpp import Iq
+from sleekxmpp.exceptions import XMPPError
from sleekxmpp.xmlstream import register_stanza_plugin
+
from stanza import Action
# Python versions before 3.0 do not use UTF-8 encoding
@@ -33,8 +35,8 @@ else:
class ActionUserBot(sleekxmpp.ClientXMPP):
"""
- A simple SleekXMPP bot that uses the adhoc command
- provided by the adhoc_provider.py example.
+ A simple SleekXMPP bot that sends a custom action stanza
+ to another client.
"""
def __init__(self, jid, password, other):
@@ -47,10 +49,10 @@ class ActionUserBot(sleekxmpp.ClientXMPP):
# and the XML streams are ready for use. We want to
# listen for this event so that we we can initialize
# our roster.
- self.add_event_handler("session_start", self.start)
+ self.add_event_handler("session_start", self.start, threaded=True)
self.add_event_handler("message", self.message)
- register_stanza_plugin(sleekxmpp.Iq, Action)
+ register_stanza_plugin(Iq, Action)
def start(self, event):
"""
@@ -71,12 +73,16 @@ class ActionUserBot(sleekxmpp.ClientXMPP):
self.send_custom_iq()
def send_custom_iq(self):
+ """Create and send two custom actions.
+
+ If the first action was successful, then send
+ a shutdown command and then disconnect.
+ """
iq = self.Iq()
iq['to'] = self.action_provider
iq['type'] = 'set'
- iq.enable('action')
iq['action']['method'] = 'is_prime'
- iq['action']['param'] = str(2)
+ iq['action']['param'] = '2'
try:
resp = iq.send()
@@ -85,12 +91,14 @@ class ActionUserBot(sleekxmpp.ClientXMPP):
iq2 = self.Iq()
iq2['to'] = self.action_provider
iq2['type'] = 'set'
- iq2.enable('action')
iq2['action']['method'] = 'bye'
- iq2.send(block = False)
- self.disconnect()
- except sleekxmpp.exceptions.XMPPError:
- pass
+ iq2.send(block=False)
+
+ # The wait=True delays the disconnect until the queue
+ # of stanzas to be sent becomes empty.
+ self.disconnect(wait=True)
+ except XMPPError:
+ print('There was an error sending the custom action.')
def message(self, msg):
"""
diff --git a/examples/custom_stanzas/stanza.py b/examples/custom_stanzas/stanza.py
index ca85a0f0..50d0f9f2 100644
--- a/examples/custom_stanzas/stanza.py
+++ b/examples/custom_stanzas/stanza.py
@@ -1,8 +1,56 @@
from sleekxmpp.xmlstream import ElementBase
class Action(ElementBase):
+
+ """
+ A stanza class for XML content of the form:
+
+
+ X
+ X
+ X
+
+ """
+
+ #: The `name` field refers to the basic XML tag name of the
+ #: stanza. Here, the tag name will be 'action'.
name = 'action'
+
+ #: The namespace of the main XML tag.
namespace = 'sleekxmpp:custom:actions'
+
+ #: The `plugin_attrib` value is the name that can be used
+ #: with a parent stanza to access this stanza. For example
+ #: from an Iq stanza object, accessing:
+ #:
+ #: iq['action']
+ #:
+ #: would reference an Action object, and will even create
+ #: an Action object and append it to the Iq stanza if
+ #: one doesn't already exist.
plugin_attrib = 'action'
+
+ #: Stanza objects expose dictionary-like interfaces for
+ #: accessing and manipulating substanzas and other values.
+ #: The set of interfaces defined here are the names of
+ #: these dictionary-like interfaces provided by this stanza
+ #: type. For example, an Action stanza object can use:
+ #:
+ #: action['method'] = 'foo'
+ #: print(action['param'])
+ #: del action['status']
+ #:
+ #: to set, get, or remove its values.
interfaces = set(('method', 'param', 'status'))
+
+ #: By default, values in the `interfaces` set are mapped to
+ #: attribute values. This can be changed such that an interface
+ #: maps to a subelement's text value by adding interfaces to
+ #: the sub_interfaces set. For example, here all interfaces
+ #: are marked as sub_interfaces, and so the XML produced will
+ #: look like:
+ #:
+ #:
+ #: foo
+ #:
sub_interfaces = interfaces