diff --git a/docs/api/exceptions.rst b/docs/api/exceptions.rst new file mode 100644 index 0000000..7bc72ce --- /dev/null +++ b/docs/api/exceptions.rst @@ -0,0 +1,14 @@ +Exceptions +========== + +.. module:: sleekxmpp.exceptions + + +.. autoexception:: XMPPError + :members: + +.. autoexception:: IqError + :members: + +.. autoexception:: IqTimeout + :members: diff --git a/docs/create_plugin.rst b/docs/create_plugin.rst index 112ef50..12efa84 100644 --- a/docs/create_plugin.rst +++ b/docs/create_plugin.rst @@ -1,3 +1,5 @@ +.. _create-plugin: + Creating a SleekXMPP Plugin =========================== diff --git a/docs/getting_started/iq.rst b/docs/getting_started/iq.rst index 7ac1508..98e0bda 100644 --- a/docs/getting_started/iq.rst +++ b/docs/getting_started/iq.rst @@ -1,2 +1,182 @@ Send/Receive IQ Stanzas ======================= + +Unlike :class:`~sleekxmpp.stanza.message.Message` and +:class:`~sleekxmpp.stanza.presence.Presence` stanzas which only use +text data for basic usage, :class:`~sleekxmpp.stanza.iq.Iq` stanzas +require using XML payloads, and generally entail creating a new +SleekXMPP plugin to provide the necessary convenience methods to +make working with them easier. + +Basic Use +--------- + +XMPP's use of :class:`~sleekxmpp.stanza.iq.Iq` stanzas is built around +namespaced ```` elements. For clients, just sending the +empty ```` element will suffice for retrieving information. For +example, a very basic implementation of service discovery would just +need to be able to send: + +.. code-block:: xml + + + + + +Creating Iq Stanzas +~~~~~~~~~~~~~~~~~~~ + +SleekXMPP provides built-in support for creating basic :class:`~sleekxmpp.stanza.iq.Iq` +stanzas this way. The relevant methods are: + +* :meth:`~sleekxmpp.basexmpp.BaseXMPP.make_iq` +* :meth:`~sleekxmpp.basexmpp.BaseXMPP.make_iq_get` +* :meth:`~sleekxmpp.basexmpp.BaseXMPP.make_iq_set` +* :meth:`~sleekxmpp.basexmpp.BaseXMPP.make_iq_result` +* :meth:`~sleekxmpp.basexmpp.BaseXMPP.make_iq_error` +* :meth:`~sleekxmpp.basexmpp.BaseXMPP.make_iq_query` + +These methods all follow the same pattern: create or modify an existing +:class:`~sleekxmpp.stanza.iq.Iq` stanza, set the ``'type'`` value based +on the method name, and finally add a ```` element with the given +namespace. For example, to produce the query above, you would use: + +.. code-block:: python + + self.make_iq_get(queryxmlns='http://jabber.org/protocol/disco#info', + ito='user@example.com') + + +Sending Iq Stanzas +~~~~~~~~~~~~~~~~~~ + +Once an :class:`~sleekxmpp.stanza.iq.Iq` stanza is created, sending it +over the wire is done using its :meth:`~sleekxmpp.stanza.iq.Iq.send()` +method, like any other stanza object. However, there are a few extra +options to control how to wait for the query's response. + +These options are: + +* ``block``: The default behaviour is that :meth:`~sleekxmpp.stanza.iq.Iq.send()` + will block until a response is received and the response stanza will be the + return value. Setting ``block`` to ``False`` will cause the call to return + immediately. In which case, you will need to arrange some way to capture + the response stanza if you need it. + +* ``timeout``: When using the blocking behaviour, the call will eventually + timeout with an error. The default timeout is 30 seconds, but this may + be overidden two ways. To change the timeout globally, set: + + .. code-block:: python + + self.response_timeout = 10 + + To change the timeout for a single call, the ``timeout`` parameter works: + + .. code-block:: python + + iq.send(timeout=60) + +* ``callback``: When not using a blocking call, using the ``callback`` + argument is a simple way to register a handler that will execute + whenever a response is finally received. Using this method, there + is no timeout limit. In case you need to remove the callback, the + name of the newly created callback is returned. + + .. code-block:: python + + cb_name = iq.send(callback=self.a_callback) + + # ... later if we need to cancel + self.remove_handler(cb_name) + +Properly working with :class:`~sleekxmpp.stanza.iq.Iq` stanzas requires +handling the intended, normal flow, error responses, and timed out +requests. To make this easier, two exceptions may be thrown by +:meth:`~sleekxmpp.stanza.iq.Iq.send()`: :exc:`~sleekxmpp.exceptions.IqError` +and :exc:`~sleekxmpp.exceptions.IqTimeout`. These exceptions only +apply to the default, blocking calls. + +.. code-block:: python + + try: + resp = iq.send() + # ... do stuff with expected Iq result + except IqError as e: + err_resp = e.iq + # ... handle error case + except IqTimeout: + # ... no response received in time + pass + +If you do not care to distinguish between errors and timeouts, then you +can combine both cases with a generic :exc:`~sleekxmpp.exceptions.XMPPError` +exception: + +.. code-block:: python + + try: + resp = iq.send() + except XMPPError: + # ... Don't care about the response + pass + +Advanced Use +------------ + +Going beyond the basics provided by SleekXMPP requires building at least a +rudimentary SleekXMPP plugin to create a :term:`stanza object` for +interfacting with the :class:`~sleekxmpp.stanza.iq.Iq` payload. + +.. seealso:: + + * :ref:`create-plugin` + * :ref:`work-with-stanzas` + * :ref:`using-handlers-matchers` + + +The typical way to respond to :class:`~sleekxmpp.stanza.iq.Iq` requests is +to register stream handlers. As an example, suppose we create a stanza class +named ``CustomXEP`` which uses the XML element ````, +and has a :attr:`~sleekxmpp.xmlstream.stanzabase.ElementBase.plugin_attrib` value +of ``custom_xep``. + +There are two types of incoming :class:`~sleekxmpp.stanza.iq.Iq` requests: +``get`` and ``set``. You can register a handler that will accept both and then +filter by type as needed, as so: + +.. code-block:: python + + self.register_handler(Callback( + 'CustomXEP Handler', + StanzaPath('iq/custom_xep'), + self._handle_custom_iq)) + + # ... + + def _handle_custom_iq(self, iq): + if iq['type'] == 'get': + # ... + pass + elif iq['type'] == 'set': + # ... + pass + else: + # ... This will capture error responses too + pass + +If you want to filter out query types beforehand, you can adjust the matching +filter by using ``@type=get`` or ``@type=set`` if you are using the recommended +:class:`~sleekxmpp.xmlstream.matcher.stanzapath.StanzaPath` matcher. + +.. code-block:: python + + self.register_handler(Callback( + 'CustomXEP Handler', + StanzaPath('iq@type=get/custom_xep'), + self._handle_custom_iq_get)) + + # ... + + def _handle_custom_iq_get(self, iq): + assert(iq['type'] == 'get') diff --git a/docs/handlersmatchers.rst b/docs/handlersmatchers.rst index 7ac750c..628c414 100644 --- a/docs/handlersmatchers.rst +++ b/docs/handlersmatchers.rst @@ -1,2 +1,4 @@ +.. _using-handlers-matchers: + Using Stream Handlers and Matchers ================================== diff --git a/docs/howto/stanzas.rst b/docs/howto/stanzas.rst index 7ca7bbf..d52a90d 100644 --- a/docs/howto/stanzas.rst +++ b/docs/howto/stanzas.rst @@ -1,3 +1,5 @@ +.. _work-with-stanzas: + How to Work with Stanza Objects =============================== diff --git a/sleekxmpp/xmlstream/stanzabase.py b/sleekxmpp/xmlstream/stanzabase.py index 389fe20..721181a 100644 --- a/sleekxmpp/xmlstream/stanzabase.py +++ b/sleekxmpp/xmlstream/stanzabase.py @@ -167,7 +167,7 @@ class ElementBase(object): #: For :class:`ElementBase` subclasses which are intended to be used #: as plugins, the ``plugin_attrib`` value defines the plugin name. #: Plugins may be accessed by using the ``plugin_attrib`` value as - #: the interface. An example using ``plugin_attrib = 'foo'``: + #: the interface. An example using ``plugin_attrib = 'foo'``:: #: #: register_stanza_plugin(Message, FooPlugin) #: msg = Message()