-
Notifications
You must be signed in to change notification settings - Fork 299
Stanza Objects
In XMPP, data is sent over a pair of XML streams. The root element for both
of these streams is <stream />
, and any direct children of this
element are referred to as stanzas. A stanza is just a chunk or fragment of XML
sent during any XMPP communications. There are three main types of stanzas:
-
<message />
(See Stanzas: Message) -
<presence />
(See Stanzas: Presence) -
<iq />
(See Stanzas: Iq)
These stanzas are part of the default jabber:client
namespace.
In SleekXMPP we have classes (also called Stanza Objects) for these three stanzas that you can look at in sleekxmpp/stanza/message.py, sleekxmpp/stanza/presence.py, sleekxmpp/stanza/iq.py, etc.
Stanza objects are XML cElementTree accessors, in essence, similar in style to Blather's except that SleekXMPP uses keys. For example:
def handleMessage(self, msg):
print (msg.keys()) # prints (to, from, type, body, subject, mucroom, mucnick)
print (msg['body']) # prints the message body
msg.reply('Thanks for sending me "%(body)s"' % msg).send()
A SleekXMPP connection instance has methods that return initialized stanzas:
sleekxmpp.Message()
sleekxmpp.Presence()
sleekxmpp.Iq()
You will also receive these stanza objects in your Event Handlers.
Stanza objects all derive from the same core classes, and so share some basic interfaces. These include:
-
stanza['id']
- Any string, but ideally unique. While the purpose for an
id
can vary from stanza to stanza, it is often used to associate a stanza with its reply.
-
stanza['type']
- Meaning varies according to stanza. For example, for an
<iq />
stanza this will be one of"get"
,"set"
,"result"
, or"error"
, but for a<message />
stanza this could be one of"chat"
,"normal"
,"groupchat"
,"headline"
, or"error"
.
-
-
stanza['to']
,stanza['from']
- These keys will always return a JID
object. But, you can still assign a simple string value of a JID, such as
usernode@serverdomain/resource
, and it will be converted automatically.
-
-
stanza['error']['type']
- The
stanza['error']
key actually returns an<error />
stanza object. The'type'
for an error can be one of:"auth"
,"cancel"
,"modify"
, or"wait"
.
-
stanza['error']['condition']
- While the error type just places the error in a broad category of
possible causes, the condition narrows down the cause of the error.
Some conditions include:
"bad-request"
,"item-not-found"
,"internal-server-error"
, and"feature-not-implemented"
, among others. XEP-0086 provides a useful table of error conditions.
-
stanza['error']['text']
- Text for describing the exact cause of the error.
-
stanza.xml
- The
cElement.Element
for the root element of the stanza, useful for appending stanzas to other XML objects.
-
stanza.enable()
- There can be many possible substanzas defined for a stanza object, but the
XML for those substanzas will not show in the final XML unless they are
first enabled. Usually, substanzas are enabled by directly interacting with
them and setting values. However, sometimes it is necessary to include a
bare version of the substanza even when there is no additional data. For
this, the
enable()
method initializes a stanza to its default state without any children elements, and will include that XML in the parent stanza's output. For example,iq['disco_info'].enable()
would generate a baredisco#info
stanza:<iq><query xmlns="http://jabber.org/protocol/disco#info" /></iq>
Note: Custom errors may be created by appending cElement.Element
objects to stanza['error'].xml
or appending other stanza objects to stanza['error']
.
Sometimes there just isn't a pre-defined stanza object that meets your needs. You might be implementing a new [[XMPP extension (XEP)|http://xmpp.org/extensions]], or creating your own sub-protocol on top of XMPP. Regardless of the reason, creating your own stanza objects is relatively simple. In fact, for many stanzas, just six lines are sufficient!
A stanza object's behavior is controlled by a set of attributes that together define the stanza's name and namespace, and the names for all of the keys that can be used with the object. To make demonstrating creating a stanza object easier, let's create a stanza object for the following stanza:
<iq type="set">
<task id="123" xmlns="example:task">
<command>python script.py</command>
<cleanup>rm temp.txt</cleanup>
<param>
<name>foo</name>
<value>fizz</value>
</param>
<param>
<name>bar</name>
<value>buzz</value>
</param>
</task>
</iq>
Here we have a <task />
element contained in an <iq />
stanza. To start
our stanza object, let's look at the attributes we need:
-
namespace
-
The namespace our stanza object lives in. In this case,
"example:task"
-
name
-
The name of the root XML element. In this case, the
<task />
element.
-
interfaces
-
A list of dictionary-like keys that can be used with the stanza object. When using
"key"
, if there exists a method of the formgetKey
,setKey
, ordelKey
(depending on context) then the result of calling that method will be returned. Otherwise, the value of the attributekey
of the main stanza element is returned if one exists.Note: The accessor methods currently use title case, and not camel case. Thus if you need to access an item named
"methodName"
you will need to usegetMethodname
.
-
plugin_attrib
-
The name to access this type of stanza. If we set this to
"task"
then we will be able to access our stanza object usingiq['task']
, just like how we can access error stanzas usingiq['error']
.
-
sub_interfaces
-
A subset of
interfaces
, but these keys map to the text of any subelements that are direct children of the main stanza element. Thus, referencingiq['task']['command']
will either executegetCommand
(if it exists) or return the value in the<command />
element of the<task />
stanza.If you need to access an element, say
elem
, that is not a direct child of the main stanza element, you will need to addgetElem
,setElem
, anddelElem
. See the note above about naming conventions.
-
subitem
-
A tuple containing the stanza object class names for any stanza objects that can be included in this stanza. For example, our
<task />
stanza contains a few<param />
elements. If we create a stanza object for<param />
, then the name of that class will go insubitem
.
So, given these attributes, let's create two stanza object classes:
Task
and Param
. While you probably would not need to
create a stanza object for the <param />
elements, this is practice
for creating substanzas.
import sleekxmpp
from sleekxmpp.xmlstream.stanzabase import ElementBase, ET, JID
from sleekxmpp.stanza.iq import Iq
class Param(ElementBase):
namespace = 'example:task'
name = 'param'
plugin_attrib = 'param'
interfaces = set(('name', 'value'))
sub_interfaces = interfaces
class Task(ElementBase):
namespace = 'example:task'
name = 'task'
plugin_attrib = 'task'
interfaces = set(('id', 'command', 'cleanup', 'params'))
sub_interfaces = set(('command', 'cleanup'))
subitem = (Param,)
We still need to add some methods to Task
for managing <param />
elements. To do so, we add getParams
, setParams
, and delParams
to
satisfy the 'params'
key in interfaces
. Also, we can add addParam
and delParam
to add or remove a single parameter.
import sleekxmpp
from sleekxmpp.xmlstream.stanzabase import ElementBase, ET, JID
from sleekxmpp.stanza.iq import Iq
class Param(ElementBase):
namespace = 'example:task'
name = 'param'
plugin_attrib = 'param'
interfaces = set(('name', 'value'))
sub_interfaces = interfaces
class Task(ElementBase):
namespace = 'example:task'
name = 'task'
plugin_attrib = 'task'
interfaces = set(('id', 'command', 'cleanup', 'params'))
sub_interfaces = set(('command', 'cleanup'))
subitem = (Param,)
def getParams(self):
params = {}
for par in self.xml.findall('{%s}param' % Param.namespace):
param = Param(par)
params[param['name']] = param['value']
return params
def setParams(self, params):
# params is a dictonary
for name in params:
self.addParam(name, params[name])
def delParams(self):
params = self.xml.findall('{%s}param' % Param.namespace)
for param in params:
self.xml.remove(param)
def addParam(self, name, value):
# Use Param(None, self) to link the param object
# with the task object.
param_obj = Param(None, self)
param_obj['name'] = name
param_obj['value'] = value
def delParam(self, name):
# Get all <param /> elements
params = self.xml.findall('{%s}param' % Param.namespace)
for parXML in params:
# Create a stanza object to test against
param = Param(parXML)
# Remove <param /> element if name matches
if param['name'] == name:
self.xml.remove(parXML)
The 'substanzas'
key returns all substanzas created from the classes in
subitem
. Thus for a task object task
, task['substanzas']
will be a
list of Param
objects.
Note: The final step is to associate the <task />
stanza with <iq />
stanzas. Given a SleekXMPP object xmpp
, we can do this by calling
registerStanzaPlugin(Iq, Task)
. See Event Handlers for an example of how
to listen for and respond to your new stanzas.</p>
Congratulations! You now have a pair of stanza objects to use. See Creating a SleekXMPP Plugin for further examples on extending SleekXMPP's functionality.