Skip to content
Lance Stout edited this page Oct 25, 2010 · 1 revision

Stanza Objects

Background

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:

These stanzas are part of the default jabber:client namespace.

Using Stanza Objects

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.

Common Features

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 bare disco#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'].

Defining Custom Stanza Objects

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 form getKey, setKey, or delKey (depending on context) then the result of calling that method will be returned. Otherwise, the value of the attribute key 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 use getMethodname.

  • 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 using iq['task'], just like how we can access error stanzas using iq['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, referencing iq['task']['command'] will either execute getCommand (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 add getElem, setElem, and delElem. 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 in subitem.

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.