How to Work with Stanza Objects#

Slixmpp provides a large variety of facilities for abstracting the underlying XML payloads of XMPP. Most of the visible user interface comes in a dict-like interface provided in a specific __getitem__ implementation for ElementBase objects.

As a very high-level example, here is how to create a stanza with an XEP-0191 payload, assuming the xep_0191 plugin is loaded:

from slixmpp.stanza import Iq
iq = Iq()
iq['to'] = 'toto@example.com'
iq['type'] = 'set'
iq['block']['items'] = {'a@example.com', 'b@example.com'}

Printing the resulting Iq object gives us the following XML (reformatted for readability):

<iq xmlns="jabber:client" id="0" to="toto@example.com" type="set">
    <block xmlns="urn:xmpp:blocking">
        <item jid="b@example.com" />
        <item jid="a@example.com" />
    </block>
</iq>

Realistically, users of the Slixmpp library should make use of the shorthand functions available in their ClientXMPP or ComponentXMPP objects to create Iq, Message or Presence objects that are bound to a stream, and which have a generated unique identifier.

The most relevant functions are:

slixmpp.BaseXMPP.make_iq_get(self, queryxmlns=None, ito=None, ifrom=None, iq=None)#

Create an Iq stanza of type 'get'.

Optionally, a query element may be added.

Parameters
  • queryxmlns (Optional[str]) – The namespace of the query to use.

  • ito (Union[str, JID, None]) – The destination JID for this stanza.

  • ifrom (Union[str, JID, None]) – The 'from' JID to use for this stanza.

  • iq (Optional[Iq]) – Optionally use an existing stanza instead of generating a new one.

Return type

Iq

slixmpp.BaseXMPP.make_iq_set(self, sub=None, ito=None, ifrom=None, iq=None)#

Create an Iq stanza of type 'set'.

Optionally, a substanza may be given to use as the stanza’s payload.

Parameters
  • sub (Union[ElementBase, Element, None]) – Either an ElementBase stanza object or an Element XML object to use as the Iq’s payload.

  • ito (Union[str, JID, None]) – The destination JID for this stanza.

  • ifrom (Union[str, JID, None]) – The 'from' JID to use for this stanza.

  • iq (Optional[Iq]) – Optionally use an existing stanza instead of generating a new one.

Return type

Iq

slixmpp.BaseXMPP.make_message(self, mto, mbody=None, msubject=None, mtype=None, mhtml=None, mfrom=None, mnick=None)#

Create and initialize a new Message stanza.

Parameters
  • mto (Union[str, JID]) – The recipient of the message.

  • mbody (Optional[str]) – The main contents of the message.

  • msubject (Optional[str]) – Optional subject for the message.

  • mtype (Optional[Literal[‘chat’, ‘error’, ‘groupchat’, ‘headline’, ‘normal’]]) – The message’s type, such as 'chat' or 'groupchat'.

  • mhtml (Optional[str]) – Optional HTML body content in the form of a string.

  • mfrom (Union[str, JID, None]) – The sender of the message. if sending from a client, be aware that some servers require that the full JID of the sender be used.

  • mnick (Optional[str]) – Optional nickname of the sender.

Return type

Message

slixmpp.BaseXMPP.make_presence(self, pshow=None, pstatus=None, ppriority=None, pto=None, ptype=None, pfrom=None, pnick=None)#

Create and initialize a new Presence stanza.

Parameters
  • pshow (Optional[str]) – The presence’s show value.

  • pstatus (Optional[str]) – The presence’s status message.

  • ppriority (Optional[int]) – This connection’s priority.

  • pto (Union[str, JID, None]) – The recipient of a directed presence.

  • ptype (Optional[Literal[‘error’, ‘probe’, ‘subscribe’, ‘subscribed’, ‘unavailable’, ‘unsubscribe’, ‘unsubscribed’]]) – The type of presence, such as 'subscribe'.

  • pfrom (Union[str, JID, None]) – The sender of the presence.

  • pnick (Optional[str]) – Optional nickname of the presence’s sender.

Return type

Presence

The previous example then becomes:

iq = xmpp.make_iq_get(ito='toto@example.com')
iq['block']['items'] = {'a@example.com', 'b@example.com'}

Note

xml:lang is handled by piping the lang name after the attribute. For example message['body|fr'] will return the <body/> attribute with xml:lang="fr.

The next sections will try to explain as clearly as possible how the magic operates.

Defining Stanza Interfaces#

The stanza interface is very rich and let developers have full control over the API they want to have to manipulate stanzas.

The entire interface is defined as class attributes that are redefined when subclassing ElementBase when creating a stanza plugin.

The main attributes defining a stanza interface:

  • plugin_attrib: str, the name of this element on the parent

  • plugin_multi_attrib: str, the name of the iterable for this element on the parent

  • interfaces: set, all known interfaces for this element

  • sub_interfaces: set (subset of interfaces), for sub-elements with only text nodes

  • bool_interfaces: set (subset of interfaces), for empty-sub-elements

  • overrides: list (subset of interfaces), for interfaces to ovverride on the parent

  • is_extension: bool, if the element is only an extension of the parent stanza

plugin_attrib#

The plugin_attrib string is the defining element of any stanza plugin, as it the name through which the element is accessed (except for overrides and is_extension).

The extension is then registered through the help of register_stanza_plugin() which will attach the plugin to its parent.

from slixmpp import ElementBase, Iq

class Payload(ElementBase):
    name = 'apayload'
    plugin_attrib = 'mypayload'
    namespace = 'x-toto'

register_stanza_plugin(Iq, Payload)

iq = Iq()
iq.enable('mypayload') # Similar to iq['mypayload']

The Iq element created now contains our custom <apayload/> element.

<iq xmlns="jabber:client" id="0">
    <apayload xmlns="x-toto"/>
</iq>

plugin_multi_attrib#

The register_stanza_plugin() function has an iterable parameter, which defaults to False. When set to True, it means that iterating over the element is possible.

class Parent(ElementBase):
    pass # does not matter

class Sub(ElementBase):
    name = 'sub'
    plugin_attrib = 'sub'

class Sub2(ElementBase):
    name = 'sub2'
    plugin_attrib = 'sub2'

register_stanza_plugin(Parent, Sub, iterable=True)
register_stanza_plugin(Parent, Sub2, iterable=True)

parent = Parent()
parent.append(Sub())
parent.append(Sub2())
parent.append(Sub2())
parent.append(Sub())

for element in parent:
    do_something # A mix of Sub and Sub2 elements

In this situation, iterating over parent will yield each of the appended elements, one after the other.

Sometimes you only want one specific type of sub-element, which is the use of the plugin_multi_attrib string interface. This name will be mapped on the parent, just like plugin_attrib, but will return a list of all elements of the same type only.

Re-using our previous example:

class Parent(ElementBase):
    pass # does not matter

class Sub(ElementBase):
    name = 'sub'
    plugin_attrib = 'sub'
    plugin_multi_attrib = 'subs'

class Sub2(ElementBase):
    name = 'sub2'
    plugin_attrib = 'sub2'
    plugin_multi_attrib = 'subs2'

register_stanza_plugin(Parent, Sub, iterable=True)
register_stanza_plugin(Parent, Sub2, iterable=True)

parent = Parent()
parent.append(Sub())
parent.append(Sub2())
parent.append(Sub2())
parent.append(Sub())

for sub in parent['subs']:
    do_something # ony Sub objects here

for sub2 in parent['subs2']:
    do_something # ony Sub2 objects here

interfaces#

The interfaces set must contain all the known ways to interact with this element. It does not include plugins (registered to the element through register_stanza_plugin()), which are dynamic.

By default, a name present in interfaces will be mapped to an attribute of the element with the same name.

class Example(Element):
    name = 'example'
    interfaces = {'toto'}

example = Example()
example['toto'] = 'titi'

In this case, example contains <example toto="titi"/>.

For empty and text_only sub-elements, there are sub_interfaces and bool_interfaces (the keys must still be in interfaces.

You can however define any getter, setter, and delete custom method for any of those interfaces. Keep in mind that if one of the three is not custom, Slixmpp will use the default one, so you have to make sure that either you redefine all get/set/del custom methods, or that your custom methods are compatible with the default ones.

In the following example, we want the toto attribute to be an integer.

class Example(Element):
    interfaces = {'toto', 'titi', 'tata'}

    def get_toto(self) -> Optional[int]:
        try:
            return int(self.xml.attrib.get('toto', ''))
        except ValueError:
            return None

    def set_toto(self, value: int):
        int(value) # make sure the value is an int
        self.xml.attrib['toto'] = str(value)

    example = Example()
    example['tata'] = "Test" # works
    example['toto'] = 1 # works
    print(type(example['toto'])) # the value is an int
    example['toto'] = "Test 2" # ValueError

One important thing to keep in mind is that the get_ methods must be resilient (when having a default value makes sense) because they are called on objects received from the network.

sub_interfaces#

The bool_interfaces set allows mapping an interface to the text node of sub-element of the current payload, with the same namespace

Here is a simple example:

class FirstLevel(ElementBase):
    name = 'first'
    namespace = 'ns'
    interfaces = {'second'}
    sub_interfaces = {'second'}

parent = FirstLevel()
parent['second'] = 'Content of second node'

Which will produces the following:

<first xmlns="ns">
    <second>Content of second node</second>
</first>

We can see that sub_interfaces allows to quickly create a sub-element and manipulate its text node without requiring a custom element, getter or setter.

bool_interfaces#

The bool_interfaces set allows mapping an interface to a direct sub-element of the current payload, with the same namespace.

Here is a simple example:

class FirstLevel(ElementBase):
    name = 'first'
    namespace = 'ns'
    interfaces = {'second'}
    bool_interfaces = {'second'}

parent = FirstLevel()
parent['second'] = True

Which will produces the following:

<first xmlns="ns">
    <second/>
</first>

We can see that bool_interfaces allows to quickly create sub-elements with no content, without the need to create a custom class or getter/setter.

overrides#

List of interfaces on the present element that should override the parent interfaces with the same name.

class Parent(ElementBase):
    name = 'parent'
    interfaces = {'toto', 'titi'}

class Sub(ElementBase):
    name = 'sub'
    plugin_attrib = name
    interfaces = {'toto', 'titi'}
    overrides = ['toto']

register_stanza_plugin(Parent, Sub)

parent = Parent()
parent['toto'] = 'test' # equivalent to parent['sub']['toto'] = "test"

is_extension#

Stanza extensions are a specific kind of stanza plugin which have the is_extension class attribute set to True.

The following code will directly plug the extension into the Message element, allowing direct access to the interface:

class MyCustomExtension(ElementBase):
    is_extension = True
    name = 'mycustom'
    namespace = 'custom-ns'
    plugin_attrib = 'mycustom'
    interfaces = {'mycustom'}

register_stanza_plugin(Message, MyCustomExtension)

With this extension, we can do the folliowing:

message = Message()
message['mycustom'] = 'toto'

Without the extension, obtaining the same results would be:

message = Message()
message['mycustom']['mycustom'] = 'toto'

The extension is therefore named extension because it extends the parent element transparently.

Creating Stanza Plugins#

A stanza plugin is a class that inherits from ElementBase, and must contain at least the following attributes:

  • name: XML element name (e.g. toto if the element is <toto/>

  • namespace: The XML namespace of the element.

  • plugin_attrib: str, the name of this element on the parent

  • interfaces: set, all known interfaces for this element

It is then registered through register_stanza_plugin() on the parent element.

Note

register_stanza_plugin() should NOT be called at the module level, because it executes code, and executing code at the module level can slow down import significantly!