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/examples/adhoc_provider.py b/examples/adhoc_provider.py index 0a7905b..4d4c361 100755 --- a/examples/adhoc_provider.py +++ b/examples/adhoc_provider.py @@ -192,14 +192,14 @@ if __name__ == '__main__': # Connect to the XMPP server and start processing XMPP stanzas. if xmpp.connect(): - # If you do not have the pydns library installed, you will need + # If you do not have the dnspython library installed, you will need # to manually specify the name of the server if it does not match # the one in the JID. For example, to use Google Talk you would # need to use: # # if xmpp.connect(('talk.google.com', 5222)): # ... - xmpp.process(threaded=False) + xmpp.process(block=True) print("Done") else: print("Unable to connect.") diff --git a/examples/adhoc_user.py b/examples/adhoc_user.py index ac157ed..0bc03c1 100755 --- a/examples/adhoc_user.py +++ b/examples/adhoc_user.py @@ -198,14 +198,14 @@ if __name__ == '__main__': # Connect to the XMPP server and start processing XMPP stanzas. if xmpp.connect(): - # If you do not have the pydns library installed, you will need + # If you do not have the dnspython library installed, you will need # to manually specify the name of the server if it does not match # the one in the JID. For example, to use Google Talk you would # need to use: # # if xmpp.connect(('talk.google.com', 5222)): # ... - xmpp.process(threaded=False) + xmpp.process(block=True) print("Done") else: print("Unable to connect.") diff --git a/examples/disco_browser.py b/examples/disco_browser.py index 0526bfc..6023dd7 100755 --- a/examples/disco_browser.py +++ b/examples/disco_browser.py @@ -188,13 +188,13 @@ if __name__ == '__main__': # Connect to the XMPP server and start processing XMPP stanzas. if xmpp.connect(): - # If you do not have the pydns library installed, you will need + # If you do not have the dnspython library installed, you will need # to manually specify the name of the server if it does not match # the one in the JID. For example, to use Google Talk you would # need to use: # # if xmpp.connect(('talk.google.com', 5222)): # ... - xmpp.process(threaded=False) + xmpp.process(block=True) else: print("Unable to connect.") diff --git a/examples/echo_client.py b/examples/echo_client.py index 7e882a4..cbb0468 100755 --- a/examples/echo_client.py +++ b/examples/echo_client.py @@ -132,14 +132,14 @@ if __name__ == '__main__': # Connect to the XMPP server and start processing XMPP stanzas. if xmpp.connect(): - # If you do not have the pydns library installed, you will need + # If you do not have the dnspython library installed, you will need # to manually specify the name of the server if it does not match # the one in the JID. For example, to use Google Talk you would # need to use: # # if xmpp.connect(('talk.google.com', 5222)): # ... - xmpp.process(threaded=False) + xmpp.process(block=True) print("Done") else: print("Unable to connect.") diff --git a/examples/echo_component.py b/examples/echo_component.py index f569e00..d8bcd75 100755 --- a/examples/echo_component.py +++ b/examples/echo_component.py @@ -116,7 +116,7 @@ if __name__ == '__main__': # Connect to the XMPP server and start processing XMPP stanzas. if xmpp.connect(): - xmpp.process(threaded=False) + xmpp.process(block=True) print("Done") else: print("Unable to connect.") diff --git a/examples/muc.py b/examples/muc.py index 96b5fb8..7af3744 100755 --- a/examples/muc.py +++ b/examples/muc.py @@ -175,14 +175,14 @@ if __name__ == '__main__': # Connect to the XMPP server and start processing XMPP stanzas. if xmpp.connect(): - # If you do not have the pydns library installed, you will need + # If you do not have the dnspython library installed, you will need # to manually specify the name of the server if it does not match # the one in the JID. For example, to use Google Talk you would # need to use: # # if xmpp.connect(('talk.google.com', 5222)): # ... - xmpp.process(threaded=False) + xmpp.process(block=True) print("Done") else: print("Unable to connect.") diff --git a/examples/ping.py b/examples/ping.py index 462b837..81194ee 100755 --- a/examples/ping.py +++ b/examples/ping.py @@ -129,14 +129,14 @@ if __name__ == '__main__': # Connect to the XMPP server and start processing XMPP stanzas. if xmpp.connect(): - # If you do not have the pydns library installed, you will need + # If you do not have the dnspython library installed, you will need # to manually specify the name of the server if it does not match # the one in the JID. For example, to use Google Talk you would # need to use: # # if xmpp.connect(('talk.google.com', 5222)): # ... - xmpp.process(threaded=False) + xmpp.process(block=True) print("Done") else: print("Unable to connect.") diff --git a/examples/proxy_echo_client.py b/examples/proxy_echo_client.py index 3466dc9..1f4ba9d 100755 --- a/examples/proxy_echo_client.py +++ b/examples/proxy_echo_client.py @@ -156,14 +156,14 @@ if __name__ == '__main__': # Connect to the XMPP server and start processing XMPP stanzas. if xmpp.connect(): - # If you do not have the pydns library installed, you will need + # If you do not have the dnspython library installed, you will need # to manually specify the name of the server if it does not match # the one in the JID. For example, to use Google Talk you would # need to use: # # if xmpp.connect(('talk.google.com', 5222)): # ... - xmpp.process(threaded=False) + xmpp.process(block=True) print("Done") else: print("Unable to connect.") diff --git a/examples/roster_browser.py b/examples/roster_browser.py index 4a58cc1..7926b09 100644 --- a/examples/roster_browser.py +++ b/examples/roster_browser.py @@ -160,14 +160,14 @@ if __name__ == '__main__': # Connect to the XMPP server and start processing XMPP stanzas. if xmpp.connect(): - # If you do not have the pydns library installed, you will need + # If you do not have the dnspython library installed, you will need # to manually specify the name of the server if it does not match # the one in the JID. For example, to use Google Talk you would # need to use: # # if xmpp.connect(('talk.google.com', 5222)): # ... - xmpp.process(threaded=False) + xmpp.process(block=True) else: print("Unable to connect.") diff --git a/examples/send_client.py b/examples/send_client.py index d1dafee..94bb584 100755 --- a/examples/send_client.py +++ b/examples/send_client.py @@ -131,14 +131,14 @@ if __name__ == '__main__': # Connect to the XMPP server and start processing XMPP stanzas. if xmpp.connect(): - # If you do not have the pydns library installed, you will need + # If you do not have the dnspython library installed, you will need # to manually specify the name of the server if it does not match # the one in the JID. For example, to use Google Talk you would # need to use: # # if xmpp.connect(('talk.google.com', 5222)): # ... - xmpp.process(threaded=False) + xmpp.process(block=True) print("Done") else: print("Unable to connect.") diff --git a/sleekxmpp/stanza/message.py b/sleekxmpp/stanza/message.py index 3518fc7..19d4d9e 100644 --- a/sleekxmpp/stanza/message.py +++ b/sleekxmpp/stanza/message.py @@ -79,7 +79,7 @@ class Message(RootStanza): return self def normal(self): - """Set the message type to 'chat'.""" + """Set the message type to 'normal'.""" self['type'] = 'normal' return self diff --git a/sleekxmpp/xmlstream/stanzabase.py b/sleekxmpp/xmlstream/stanzabase.py index 8678ca1..d140970 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()