docs: fill the stanza howto
This commit is contained in:
		@@ -3,28 +3,411 @@
 | 
			
		||||
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 :class:`~slixmpp.xmlstream.ElementBase` objects.
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
As a very high-level example, here is how to create a stanza with
 | 
			
		||||
an XEP-0191 payload, assuming the :class:`xep_0191 <slixmpp.plugins.xep_0191.XEP_0191>`
 | 
			
		||||
plugin is loaded:
 | 
			
		||||
 | 
			
		||||
.. code-block:: python
 | 
			
		||||
 | 
			
		||||
    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 :class:`~slixmpp.stanaz.Iq` object gives us the
 | 
			
		||||
following XML (reformatted for readability):
 | 
			
		||||
 | 
			
		||||
.. code-block:: xml
 | 
			
		||||
 | 
			
		||||
    <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 :class:`~.ClientXMPP` or
 | 
			
		||||
:class:`~.ComponentXMPP` objects to create :class:`~.Iq`, :class:`~.Message`
 | 
			
		||||
or :class:`~.Presence` objects that are bound to a stream, and which have
 | 
			
		||||
a generated unique identifier.
 | 
			
		||||
 | 
			
		||||
The most relevant functions are:
 | 
			
		||||
 | 
			
		||||
.. autofunction:: slixmpp.BaseXMPP.make_iq_get
 | 
			
		||||
 | 
			
		||||
.. autofunction:: slixmpp.BaseXMPP.make_iq_set
 | 
			
		||||
 | 
			
		||||
.. autofunction:: slixmpp.BaseXMPP.make_message
 | 
			
		||||
 | 
			
		||||
.. autofunction:: slixmpp.BaseXMPP.make_presence
 | 
			
		||||
 | 
			
		||||
The previous example then becomes:
 | 
			
		||||
 | 
			
		||||
.. code-block:: python
 | 
			
		||||
 | 
			
		||||
    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.
 | 
			
		||||
 | 
			
		||||
.. _create-stanza-interfaces:
 | 
			
		||||
 | 
			
		||||
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 :class:`~.ElementBase` when `creating a stanza plugin <create-stanza-plugins>`_.
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
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:
 | 
			
		||||
 | 
			
		||||
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 :func:`~.register_stanza_plugin`
 | 
			
		||||
which will attach the plugin to its parent.
 | 
			
		||||
 | 
			
		||||
.. code-block:: python
 | 
			
		||||
 | 
			
		||||
    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 :class:`~.Iq` element created now contains our custom ``<apayload/>`` element.
 | 
			
		||||
 | 
			
		||||
.. code-block:: xml
 | 
			
		||||
 | 
			
		||||
    <iq xmlns="jabber:client" id="0">
 | 
			
		||||
        <apayload xmlns="x-toto"/>
 | 
			
		||||
    </iq>
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
.. _plugin_multi_attrib:
 | 
			
		||||
 | 
			
		||||
plugin_multi_attrib
 | 
			
		||||
~~~~~~~~~~~~~~~~~~~
 | 
			
		||||
 | 
			
		||||
The :func:`~.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.
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
.. code-block:: python
 | 
			
		||||
 | 
			
		||||
    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:
 | 
			
		||||
 | 
			
		||||
.. code-block:: python
 | 
			
		||||
 | 
			
		||||
    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:
 | 
			
		||||
 | 
			
		||||
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
 | 
			
		||||
:func:`~.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.
 | 
			
		||||
 | 
			
		||||
.. code-block:: python
 | 
			
		||||
 | 
			
		||||
    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.
 | 
			
		||||
 | 
			
		||||
.. code-block:: python
 | 
			
		||||
 | 
			
		||||
    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:
 | 
			
		||||
 | 
			
		||||
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:
 | 
			
		||||
 | 
			
		||||
.. code-block:: python
 | 
			
		||||
 | 
			
		||||
    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:
 | 
			
		||||
 | 
			
		||||
.. code-block:: xml
 | 
			
		||||
 | 
			
		||||
    <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:
 | 
			
		||||
 | 
			
		||||
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:
 | 
			
		||||
 | 
			
		||||
.. code-block:: python
 | 
			
		||||
 | 
			
		||||
    class FirstLevel(ElementBase):
 | 
			
		||||
        name = 'first'
 | 
			
		||||
        namespace = 'ns'
 | 
			
		||||
        interfaces = {'second'}
 | 
			
		||||
        bool_interfaces = {'second'}
 | 
			
		||||
 | 
			
		||||
    parent = FirstLevel()
 | 
			
		||||
    parent['second'] = True
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
Which will produces the following:
 | 
			
		||||
 | 
			
		||||
.. code-block:: xml
 | 
			
		||||
 | 
			
		||||
    <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.
 | 
			
		||||
 | 
			
		||||
.. code-block:: python
 | 
			
		||||
 | 
			
		||||
    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
 | 
			
		||||
:class:`~.Message` element, allowing direct access
 | 
			
		||||
to the interface:
 | 
			
		||||
 | 
			
		||||
.. code-block:: python
 | 
			
		||||
 | 
			
		||||
    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:
 | 
			
		||||
 | 
			
		||||
.. code-block:: python
 | 
			
		||||
 | 
			
		||||
    message = Message()
 | 
			
		||||
    message['mycustom'] = 'toto'
 | 
			
		||||
 | 
			
		||||
Without the extension, obtaining the same results would be:
 | 
			
		||||
 | 
			
		||||
.. code-block:: python
 | 
			
		||||
 | 
			
		||||
    message = Message()
 | 
			
		||||
    message['mycustom']['mycustom'] = 'toto'
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
The extension is therefore named extension because it extends the
 | 
			
		||||
parent element transparently.
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
.. _create-stanza-plugins:
 | 
			
		||||
 | 
			
		||||
Creating Stanza Plugins
 | 
			
		||||
-----------------------
 | 
			
		||||
 | 
			
		||||
A stanza plugin is a class that inherits from :class:`~.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
 | 
			
		||||
 | 
			
		||||
.. _create-extension-plugins:
 | 
			
		||||
It is then registered through :func:`~.register_stanza_plugin` on the parent
 | 
			
		||||
element.
 | 
			
		||||
 | 
			
		||||
Creating a Stanza Extension
 | 
			
		||||
---------------------------
 | 
			
		||||
.. note::
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
.. _override-parent-interfaces:
 | 
			
		||||
 | 
			
		||||
Overriding a Parent Stanza
 | 
			
		||||
--------------------------
 | 
			
		||||
    :func:`~.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!
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user