From c66a4d4097a249efc029b761d6150378a54bf702 Mon Sep 17 00:00:00 2001 From: mathieui Date: Tue, 24 Feb 2015 18:58:40 +0100 Subject: Update the documentation and examples - update most of the examples with slixmpp - change the help channels pointed out in the doc - add a page listing differences from slixmpp and how to use asyncio nicely with slixmpp - fix some in-code rst documentation --- docs/api/stanza/iq.rst | 8 +++ docs/api/stanza/message.rst | 7 ++ docs/api/stanza/presence.rst | 8 +++ docs/api/stanza/rootstanza.rst | 8 +++ docs/api/xmlstream/filesocket.rst | 12 ---- docs/api/xmlstream/handler.rst | 8 ++- docs/api/xmlstream/jid.rst | 2 +- docs/api/xmlstream/scheduler.rst | 11 ---- docs/api/xmlstream/stanzabase.rst | 4 +- docs/api/xmlstream/tostring.rst | 5 +- docs/architecture.rst | 97 +++++++++++----------------- docs/conf.py | 4 +- docs/create_plugin.rst | 25 ++++---- docs/differences.rst | 47 ++++++++++++++ docs/getting_started/component.rst | 14 +--- docs/getting_started/echobot.rst | 53 ++------------- docs/getting_started/muc.rst | 14 +--- docs/getting_started/proxy.rst | 6 +- docs/getting_started/sendlogout.rst | 6 +- docs/glossary.rst | 17 +++-- docs/index.rst | 85 ++++++++++-------------- docs/using_asyncio.rst | 125 ++++++++++++++++++++++++++++++++++++ examples/IoT_TestDevice.py | 14 +--- examples/disco_browser.py | 20 +++--- examples/download_avatars.py | 29 ++++++--- examples/migrate_roster.py | 4 +- examples/ping.py | 10 +-- examples/pubsub_client.py | 26 ++++++-- examples/register_account.py | 2 +- examples/roster_browser.py | 13 ++-- examples/rpc_async.py | 2 +- examples/set_avatar.py | 46 ++++++------- slixmpp/stanza/iq.py | 70 +++++++++----------- slixmpp/stanza/message.py | 50 ++++++++------- slixmpp/stanza/presence.py | 43 +++++-------- slixmpp/xmlstream/stanzabase.py | 6 +- 36 files changed, 499 insertions(+), 402 deletions(-) create mode 100644 docs/api/stanza/iq.rst create mode 100644 docs/api/stanza/message.rst create mode 100644 docs/api/stanza/presence.rst create mode 100644 docs/api/stanza/rootstanza.rst delete mode 100644 docs/api/xmlstream/filesocket.rst delete mode 100644 docs/api/xmlstream/scheduler.rst create mode 100644 docs/differences.rst create mode 100644 docs/using_asyncio.rst diff --git a/docs/api/stanza/iq.rst b/docs/api/stanza/iq.rst new file mode 100644 index 00000000..0a7d7ffb --- /dev/null +++ b/docs/api/stanza/iq.rst @@ -0,0 +1,8 @@ +IQ Stanza +========= + +.. module:: slixmpp.stanza + +.. autoclass:: Iq + :members: + diff --git a/docs/api/stanza/message.rst b/docs/api/stanza/message.rst new file mode 100644 index 00000000..f01c62a7 --- /dev/null +++ b/docs/api/stanza/message.rst @@ -0,0 +1,7 @@ +Message Stanza +============== + +.. module:: slixmpp.stanza + +.. autoclass:: Message + :members: diff --git a/docs/api/stanza/presence.rst b/docs/api/stanza/presence.rst new file mode 100644 index 00000000..15ac7bf9 --- /dev/null +++ b/docs/api/stanza/presence.rst @@ -0,0 +1,8 @@ +Presence Stanza +=============== + +.. module:: slixmpp.stanza + +.. autoclass:: Presence + :members: + diff --git a/docs/api/stanza/rootstanza.rst b/docs/api/stanza/rootstanza.rst new file mode 100644 index 00000000..83d9f49b --- /dev/null +++ b/docs/api/stanza/rootstanza.rst @@ -0,0 +1,8 @@ +Root Stanza +=========== + +.. module:: slixmpp.stanza.rootstanza + +.. autoclass:: RootStanza + :members: + diff --git a/docs/api/xmlstream/filesocket.rst b/docs/api/xmlstream/filesocket.rst deleted file mode 100644 index eff062d8..00000000 --- a/docs/api/xmlstream/filesocket.rst +++ /dev/null @@ -1,12 +0,0 @@ -.. module:: slixmpp.xmlstream.filesocket - -.. _filesocket: - -Python 2.6 File Socket Shims -============================ - -.. autoclass:: FileSocket - :members: - -.. autoclass:: Socket26 - :members: diff --git a/docs/api/xmlstream/handler.rst b/docs/api/xmlstream/handler.rst index ab8091a3..9d2f5aba 100644 --- a/docs/api/xmlstream/handler.rst +++ b/docs/api/xmlstream/handler.rst @@ -10,15 +10,19 @@ The Basic Handler Callback -------- -.. module:: slixmpp.xmlstream.handler.callback +.. module:: slixmpp.xmlstream.handler .. autoclass:: Callback :members: +CoroutineCallback +----------------- + +.. autoclass:: CoroutineCallback + :members: Waiter ------ -.. module:: slixmpp.xmlstream.handler.waiter .. autoclass:: Waiter :members: diff --git a/docs/api/xmlstream/jid.rst b/docs/api/xmlstream/jid.rst index 1844b75a..2f0c65d0 100644 --- a/docs/api/xmlstream/jid.rst +++ b/docs/api/xmlstream/jid.rst @@ -1,7 +1,7 @@ Jabber IDs (JID) ================= -.. module:: slixmpp.xmlstream.jid +.. module:: slixmpp.jid .. autoclass:: JID :members: diff --git a/docs/api/xmlstream/scheduler.rst b/docs/api/xmlstream/scheduler.rst deleted file mode 100644 index 59752eca..00000000 --- a/docs/api/xmlstream/scheduler.rst +++ /dev/null @@ -1,11 +0,0 @@ -========= -Scheduler -========= - -.. module:: slixmpp.xmlstream.scheduler - -.. autoclass:: Task - :members: - -.. autoclass:: Scheduler - :members: diff --git a/docs/api/xmlstream/stanzabase.rst b/docs/api/xmlstream/stanzabase.rst index 216ceebe..ad43a44a 100644 --- a/docs/api/xmlstream/stanzabase.rst +++ b/docs/api/xmlstream/stanzabase.rst @@ -61,8 +61,8 @@ interacting with a given :term:`stanza` a :term:`stanza object`. To make dealing with more complicated and nested :term:`stanzas ` or XML chunks easier, :term:`stanza objects ` can be composed in two ways: as iterable child objects or as plugins. Iterable -child stanzas, or :term:`substanzas`, are accessible through a special -``'substanzas'`` interface. This option is useful for stanzas which +child stanzas, or :term:`substanzas `, are accessible through a +special ``'substanzas'`` interface. This option is useful for stanzas which may contain more than one of the same kind of element. When there is only one child element, the plugin method is more useful. For plugins, a parent stanza object delegates one of its XML child elements to the diff --git a/docs/api/xmlstream/tostring.rst b/docs/api/xmlstream/tostring.rst index 8d75f1db..68abbdb6 100644 --- a/docs/api/xmlstream/tostring.rst +++ b/docs/api/xmlstream/tostring.rst @@ -28,7 +28,7 @@ namespace because that is already declared by the stream header. But, if you create a :class:`~slixmpp.stanza.message.Message` instance and dump it to the terminal, the ``jabber:client`` namespace will appear. -.. autofunction:: tostring +.. autofunction:: slixmpp.xmlstream.tostring Escaping Special Characters --------------------------- @@ -43,4 +43,5 @@ In the future, the use of CDATA sections may be allowed to reduce the size of escaped text or for when other XMPP processing agents do not undertand these entities. -.. autofunction:: xml_escape +.. + autofunction:: xml_escape diff --git a/docs/architecture.rst b/docs/architecture.rst index cfc56f3e..75b70c8a 100644 --- a/docs/architecture.rst +++ b/docs/architecture.rst @@ -24,21 +24,20 @@ patterns is received; these callbacks are also referred to as :term:`stream handlers `. The class also provides a basic eventing system which can be triggered either manually or on a timed schedule. -The Main Threads -~~~~~~~~~~~~~~~~ -:class:`~slixmpp.xmlstream.xmlstream.XMLStream` instances run using at -least three background threads: the send thread, the read thread, and the -scheduler thread. The send thread is in charge of monitoring the send queue -and writing text to the outgoing XML stream. The read thread pulls text off -of the incoming XML stream and stores the results in an event queue. The -scheduler thread is used to emit events after a given period of time. - -Additionally, the main event processing loop may be executed in its -own thread if Slixmpp is being used in the background for another -application. - -Short-lived threads may also be spawned as requested for threaded -:term:`event handlers `. +The event loop +~~~~~~~~~~~~~~ +:class:`~slixmpp.xmlstream.xmlstream.XMLStream` instances inherit the +:class:`asyncio.BaseProtocol` class, and therefore do not have to handle +reads and writes directly, but receive data through +:meth:`~slixmpp.xmlstream.xmlstream.XMLStream.data_received` and write +data in the socket transport. + +Upon receiving data, :term:`stream handlers ` are run +immediately, except if they are coroutines, in which case they are +scheduled using :meth:`asyncio.async`. + +:term:`Event handlers ` (which are called inside +:term:`stream handlers `) work the same way. How XML Text is Turned into Action ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -53,7 +52,7 @@ when this bit of XML is received (with an assumed namespace of -1. **Convert XML strings into objects.** +#. **Convert XML strings into objects.** Incoming text is parsed and converted into XML objects (using ElementTree) which are then wrapped into what are referred to as @@ -66,65 +65,43 @@ when this bit of XML is received (with an assumed namespace of ``{jabber:client}message`` is associated with the class :class:`~slixmpp.stanza.Message`. -2. **Match stanza objects to callbacks.** +#. **Match stanza objects to callbacks.** These objects are then compared against the stored patterns associated - with the registered callback handlers. For each match, a copy of the - :term:`stanza object` is paired with a reference to the handler and - placed into the event queue. - - Our :class:`~slixmpp.stanza.Message` object is thus paired with the message stanza handler - :meth:`BaseXMPP._handle_message` to create the tuple:: + with the registered callback handlers. - ('stanza', stanza_obj, handler) + Each handler matching our :term:`stanza object` is then added to a list. -3. **Process the event queue.** +#. **Processing callbacks** - The event queue is the heart of Slixmpp. Nearly every action that - takes place is first inserted into this queue, whether that be received - stanzas, custom events, or scheduled events. + Every handler in the list is then called with the :term:`stanza object` + as a parameter; if the handler is a + :class:`~slixmpp.xmlstream.handler.CoroutineCallback` + then it will be scheduled in the event loop using :meth:`asyncio.async` + instead of run. - When the stanza is pulled out of the event queue with an associated - callback, the callback function is executed with the stanza as its only - parameter. - - .. warning:: - The callback, aka :term:`stream handler`, is executed in the main event - processing thread. If the handler blocks, event processing will also - block. - -4. **Raise Custom Events** +#. **Raise Custom Events** Since a :term:`stream handler` shouldn't block, if extensive processing for a stanza is required (such as needing to send and receive an :class:`~slixmpp.stanza.Iq` stanza), then custom events must be used. These events are not explicitly tied to the incoming XML stream and may - be raised at any time. Importantly, these events may be handled in their - own thread. + be raised at any time. - When the event is raised, a copy of the stanza is created for each - handler registered for the event. In contrast to :term:`stream handlers - `, these functions are referred to as :term:`event - handlers `. Each stanza/handler pair is then put into the - event queue. + In contrast to :term:`stream handlers `, these functions + are referred to as :term:`event handlers `. The code for :meth:`BaseXMPP._handle_message` follows this pattern, and - raises a ``'message'`` event:: - - self.event('message', msg) + raises a ``'message'`` event - The event call then places the message object back into the event queue - paired with an :term:`event handler`:: + .. code-block:: python - ('event', 'message', msg_copy1, custom_event_handler_1) - ('event', 'message', msg_copy2, custom_evetn_handler_2) + self.event('message', msg) -5. **Process Custom Events** +#. **Process Custom Events** - The stanza and :term:`event handler` are then pulled from the event - queue, and the handler is executed, passing the stanza as its only - argument. If the handler was registered as threaded, then a new thread - will be spawned for it. + The :term:`event handlers ` are then executed, passing + the stanza as the only argument. .. note:: Events may be raised without needing :term:`stanza objects `. @@ -135,9 +112,9 @@ when this bit of XML is received (with an assumed namespace of Finally, after a long trek, our message is handed off to the user's custom handler in order to do awesome stuff:: - msg.reply() - msg['body'] = "Hey! This is awesome!" - msg.send() + reply = msg.reply() + reply['body'] = "Hey! This is awesome!" + reply.send() .. index:: BaseXMPP, XMLStream diff --git a/docs/conf.py b/docs/conf.py index fbb61b37..898d00cd 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -105,7 +105,7 @@ html_theme = 'haiku' # The name for this set of Sphinx documents. If None, it defaults to # " v documentation". -html_title = 'Slixmpp' +html_title = 'slixmpp' # A shorter title for the navigation bar. Default is the same as html_title. html_short_title = '%s Documentation' % release @@ -219,4 +219,4 @@ man_pages = [ [u'Nathan Fritz, Lance Stout'], 1) ] -intersphinx_mapping = {'python': ('http://docs.python.org/3.2', 'python-objects.inv')} +intersphinx_mapping = {'python': ('http://docs.python.org/3.4', 'python-objects.inv')} diff --git a/docs/create_plugin.rst b/docs/create_plugin.rst index db3b4f0f..9bfb053f 100644 --- a/docs/create_plugin.rst +++ b/docs/create_plugin.rst @@ -634,8 +634,9 @@ with some additional registration fields implemented. if self.backend.register(iq['from'].bare, iq['register']): # Successful registration self.xmpp.event('registered_user', iq) - iq.reply().set_payload(iq['register'].xml) - iq.send() + reply = iq.reply() + reply.set_payload(iq['register'].xml) + reply.send() else: # Conflicting registration self._sendError(iq, '409', 'cancel', 'conflict', @@ -666,14 +667,16 @@ with some additional registration fields implemented. # Add a blank field reg.addField(field) - iq.reply().set_payload(reg.xml) - iq.send() + reply = iq.reply() + reply.set_payload(reg.xml) + reply.send() def _sendError(self, iq, code, error_type, name, text=''): - iq.reply().set_payload(iq['register'].xml) - iq.error() - iq['error']['code'] = code - iq['error']['type'] = error_type - iq['error']['condition'] = name - iq['error']['text'] = text - iq.send() + reply = iq.reply() + reply.set_payload(iq['register'].xml) + reply.error() + reply['error']['code'] = code + reply['error']['type'] = error_type + reply['error']['condition'] = name + reply['error']['text'] = text + reply.send() diff --git a/docs/differences.rst b/docs/differences.rst new file mode 100644 index 00000000..e6e26d2c --- /dev/null +++ b/docs/differences.rst @@ -0,0 +1,47 @@ +.. _differences: + +Differences from SleekXMPP +========================== + +**Python 3.4+ only** + slixmpp will only work on python 3.4 and above. + +**Stanza copies** + The same stanza object is given through all the handlers; a handler that + edits the stanza object should make its own copy. + +**Replies** + Because stanzas are not copied anymore, + :meth:`Stanza.reply() <.StanzaBase.reply>` calls + (for :class:`IQs <.Iq>`, :class:`Messages <.Message>`, etc) + now return a new object instead of editing the stanza object + in-place. + +**Block and threaded arguments** + All the functions that had a ``threaded=`` or ``block=`` argument + do not have it anymore. Also, :meth:`.Iq.send` **does not block + anymore**. + +**Coroutine facilities** + **See** :ref:`using_asyncio` + + If an event handler is a coroutine, it will be called asynchronously + in the event loop instead of inside the event caller. + + A CoroutineCallback class has been added to create coroutine stream + handlers, which will be also handled in the event loop. + + The :class:`~.slixmpp.stanza.Iq` object’s :meth:`~.slixmpp.stanza.Iq.send` + method now takes a *coroutine* parameter which, if set to ``True``, + will return a coroutine which will (asyncio-)block until the reply + is received. + + Many plugins (WIP) calls which retrieve information also accept this + ``coroutine`` parameter. + +**Architectural differences** + slixmpp does not have an event queue anymore, and instead processes + handlers directly after receiving the XML stanza. + +.. note:: + If you find something that doesn’t work but should, please report it. diff --git a/docs/getting_started/component.rst b/docs/getting_started/component.rst index 484a8e84..34aeda26 100644 --- a/docs/getting_started/component.rst +++ b/docs/getting_started/component.rst @@ -7,19 +7,11 @@ Create and Run a Server Component .. note:: If you have any issues working through this quickstart guide - or the other tutorials here, please either send a message to the - `mailing list `_ - or join the chat room at `sleek@conference.jabber.org - `_. + join the chat room at `slixmpp@muc.poez.io + `_. If you have not yet installed Slixmpp, do so now by either checking out a version -from `Github `_, or installing it using ``pip`` -or ``easy_install``. - -.. code-block:: sh - - pip install slixmpp # Or: easy_install slixmpp - +with `Git `_. Many XMPP applications eventually graduate to requiring to run as a server component in order to meet scalability requirements. To demonstrate how to diff --git a/docs/getting_started/echobot.rst b/docs/getting_started/echobot.rst index 013d6816..bb40a0b5 100644 --- a/docs/getting_started/echobot.rst +++ b/docs/getting_started/echobot.rst @@ -7,19 +7,11 @@ Slixmpp Quickstart - Echo Bot .. note:: If you have any issues working through this quickstart guide - or the other tutorials here, please either send a message to the - `mailing list `_ - or join the chat room at `sleek@conference.jabber.org - `_. + join the chat room at `slixmpp@muc.poez.io + `_. If you have not yet installed Slixmpp, do so now by either checking out a version -from `Github `_, or installing it using ``pip`` -or ``easy_install``. - -.. code-block:: sh - - pip install slixmpp # Or: easy_install slixmpp - +with `Git `_. As a basic starting project, we will create an echo bot which will reply to any messages sent to it. We will also go through adding some basic command line configuration @@ -44,6 +36,7 @@ To get started, here is a brief outline of the structure that the final project # -*- coding: utf-8 -*- import sys + import asyncio import logging import getpass from optparse import OptionParser @@ -59,24 +52,6 @@ To get started, here is a brief outline of the structure that the final project '''Finally, we connect the bot and start listening for messages''' -Default Encoding ----------------- -XMPP requires support for UTF-8 and so Slixmpp must use UTF-8 as well. In -Python3 this is simple because Unicode is the default string type. For Python2.6+ -the situation is not as easy because standard strings are simply byte arrays and -use ASCII. We can get Python to use UTF-8 as the default encoding by including: - -.. code-block:: python - - if sys.version_info < (3, 0): - from slixmpp.util.misc_ops import setdefaultencoding - setdefaultencoding('utf8') - -.. warning:: - - Until we are able to ensure that Slixmpp will always use Unicode in Python2.6+, this - may cause issues embedding Slixmpp into other applications which assume ASCII encoding. - Creating the EchoBot Class -------------------------- @@ -313,9 +288,9 @@ the ``EchoBot.__init__`` method instead. xmpp.ssl_version = ssl.PROTOCOL_SSLv3 Now we're ready to connect and begin echoing messages. If you have the package -``dnspython`` installed, then the :meth:`slixmpp.clientxmpp.ClientXMPP` method +``aiodns`` installed, then the :meth:`slixmpp.clientxmpp.ClientXMPP` method will perform a DNS query to find the appropriate server to connect to for the -given JID. If you do not have ``dnspython``, then Slixmpp will attempt to +given JID. If you do not have ``aiodns``, then Slixmpp will attempt to connect to the hostname used by the JID, unless an address tuple is supplied to :meth:`slixmpp.clientxmpp.ClientXMPP`. @@ -330,22 +305,6 @@ to :meth:`slixmpp.clientxmpp.ClientXMPP`. else: print('Unable to connect') -.. note:: - - For Google Talk users withouth ``dnspython`` installed, the above code - should look like: - - .. code-block:: python - - if __name__ == '__main__': - - # .. option parsing & echo bot configuration - - if xmpp.connect(('talk.google.com', 5222)): - xmpp.process(block=True) - else: - print('Unable to connect') - To begin responding to messages, you'll see we called :meth:`slixmpp.basexmpp.BaseXMPP.process` which will start the event handling, send queue, and XML reader threads. It will also call the :meth:`slixmpp.plugins.base.BasePlugin.post_init` method on all registered plugins. By diff --git a/docs/getting_started/muc.rst b/docs/getting_started/muc.rst index 8e41790f..4dd1ff93 100644 --- a/docs/getting_started/muc.rst +++ b/docs/getting_started/muc.rst @@ -7,19 +7,11 @@ Mulit-User Chat (MUC) Bot .. note:: If you have any issues working through this quickstart guide - or the other tutorials here, please either send a message to the - `mailing list `_ - or join the chat room at `sleek@conference.jabber.org - `_. + join the chat room at `slixmpp@muc.poez.io + `_. If you have not yet installed Slixmpp, do so now by either checking out a version -from `Github `_, or installing it using ``pip`` -or ``easy_install``. - -.. code-block:: sh - - pip install slixmpp # Or: easy_install slixmpp - +from `Git `_. Now that you've got the basic gist of using Slixmpp by following the echobot example (:ref:`echobot`), we can use one of the bundled plugins diff --git a/docs/getting_started/proxy.rst b/docs/getting_started/proxy.rst index e45b2b3a..22439d4e 100644 --- a/docs/getting_started/proxy.rst +++ b/docs/getting_started/proxy.rst @@ -7,10 +7,8 @@ Enable HTTP Proxy Support .. note:: If you have any issues working through this quickstart guide - or the other tutorials here, please either send a message to the - `mailing list `_ - or join the chat room at `sleek@conference.jabber.org - `_. + join the chat room at `slixmpp@muc.poez.io + `_. In some instances, you may wish to route XMPP traffic through an HTTP proxy, probably to get around restrictive firewalls. diff --git a/docs/getting_started/sendlogout.rst b/docs/getting_started/sendlogout.rst index 7669e340..d5882c42 100644 --- a/docs/getting_started/sendlogout.rst +++ b/docs/getting_started/sendlogout.rst @@ -4,10 +4,8 @@ Sign in, Send a Message, and Disconnect .. note:: If you have any issues working through this quickstart guide - or the other tutorials here, please either send a message to the - `mailing list `_ - or join the chat room at `sleek@conference.jabber.org - `_. + join the chat room at `slixmpp@muc.poez.io + `_. A common use case for Slixmpp is to send one-off messages from time to time. For example, one use case could be sending out a notice when diff --git a/docs/glossary.rst b/docs/glossary.rst index 35d2dc86..435df102 100644 --- a/docs/glossary.rst +++ b/docs/glossary.rst @@ -9,21 +9,20 @@ Glossary stream handler A callback function that accepts stanza objects pulled directly from the XML stream. A stream handler is encapsulated in a - object that includes a :term:`Matcher` object, and which provides - additional semantics. For example, the ``Waiter`` handler wrapper - blocks thread execution until a matching stanza is received. + object that includes a :class:`Matcher <.MatcherBase>` object, and + which provides additional semantics. For example, the + :class:`.Waiter` handler wrapper blocks thread execution until a + matching stanza is received. event handler A callback function that responds to events raised by - ``XMLStream.event``. An event handler may be marked as - threaded, allowing it to execute outside of the main processing - loop. + :meth:`.XMLStream.event`. stanza object - Informally may refer both to classes which extend ``ElementBase`` - or ``StanzaBase``, and to objects of such classes. + Informally may refer both to classes which extend :class:`.ElementBase` + or :class:`.StanzaBase`, and to objects of such classes. - A stanza object is a wrapper for an XML object which exposes ``dict`` + A stanza object is a wrapper for an XML object which exposes :class:`dict` like interfaces which may be assigned to, read from, or deleted. stanza plugin diff --git a/docs/index.rst b/docs/index.rst index 3e97fb09..5e60b1f9 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -3,38 +3,25 @@ Slixmpp .. sidebar:: Get the Code - .. code-block:: sh + The latest source code for Slixmpp may be found on the `Git repo + `_. :: - pip install slixmpp + git clone git://git.poez.io/slixmpp - The latest source code for Slixmpp may be found on `Github - `_. Releases can be found in the - ``master`` branch, while the latest development version is in the - ``develop`` branch. - - **Latest Stable Release** - - `1.0 `_ - - **Develop Releases** - - `Latest Develop Version `_ - - - A mailing list and XMPP chat room are available for discussing and getting - help with Slixmpp. - - **Mailing List** - `Slixmpp Discussion on Google Groups `_ + An XMPP chat room is available for discussing and getting help with slixmpp. **Chat** - `sleek@conference.jabber.org `_ + `slixmpp@muc.poez.io `_ + + **Reporting bugs** + You can report bugs at http://dev.louiz.org/projects/slixmpp/issues. +.. note:: + slixmpp is a friendly fork of `SleekXMPP `_ + which goal is to use asyncio instead of threads to handle networking. See + :ref:`differences`. -Slixmpp is an :ref:`MIT licensed ` XMPP library for Python 2.6/3.1+, -and is featured in examples in -`XMPP: The Definitive Guide `_ -by Kevin Smith, Remko Tronçon, and Peter Saint-Andre. If you've arrived -here from reading the Definitive Guide, please see the notes on updating -the examples to the latest version of Slixmpp. +Slixmpp is an :ref:`MIT licensed ` XMPP library for Python 3.4+, Slixmpp's design goals and philosphy are: @@ -59,11 +46,13 @@ Slixmpp's design goals and philosphy are: sensible defaults and appropriate abstractions. XML can be ugly to work with, but it doesn't have to be that way. + Here's your first Slixmpp Bot: -------------------------------- .. code-block:: python + import asyncio import logging from slixmpp import ClientXMPP @@ -85,27 +74,13 @@ Here's your first Slixmpp Bot: # Here's how to access plugins once you've registered them: # self['xep_0030'].add_feature('echo_demo') - # If you are working with an OpenFire server, you will - # need to use a different SSL version: - # import ssl - # self.ssl_version = ssl.PROTOCOL_SSLv3 - def session_start(self, event): self.send_presence() self.get_roster() # Most get_*/set_* methods from plugins use Iq stanzas, which - # can generate IqError and IqTimeout exceptions - # - # try: - # self.get_roster() - # except IqError as err: - # logging.error('There was an error getting the roster') - # logging.error(err.iq['error']['condition']) - # self.disconnect() - # except IqTimeout: - # logging.error('Server is taking too long to respond') - # self.disconnect() + # are sent asynchronously. You can almost always provide a + # callback that will be executed when the reply is received. def message(self, msg): if msg['type'] in ('chat', 'normal'): @@ -121,9 +96,18 @@ Here's your first Slixmpp Bot: xmpp = EchoBot('somejid@example.com', 'use_getpass') xmpp.connect() - xmpp.process(block=True) + xmpp.process() +To read if you come from SleekXMPP +---------------------------------- + +.. toctree:: + :maxdepth: 1 + + differences + using_asyncio + Getting Started (with Examples) ------------------------------- @@ -145,7 +129,6 @@ Tutorials, FAQs, and How To Guides .. toctree:: :maxdepth: 1 - faq xeps xmpp_tdg howto/stanzas @@ -184,9 +167,7 @@ API Reference api/xmlstream/handler api/xmlstream/matcher api/xmlstream/xmlstream - api/xmlstream/scheduler api/xmlstream/tostring - api/xmlstream/filesocket Core Stanzas ~~~~~~~~~~~~ @@ -197,8 +178,6 @@ Core Stanzas api/stanza/message api/stanza/presence api/stanza/iq - api/stanza/error - api/stanza/stream_error Plugins ~~~~~~~ @@ -220,8 +199,14 @@ Additional Info * :ref:`modindex` * :ref:`search` -Credits -------- +SleekXMPP Credits +----------------- + +.. note:: + Those people made SleekXMPP, so you should not bother them if + you have an issue with slixmpp. But it’s still fair to credit + them for their work. + **Main Author:** `Nathan Fritz `_ `fritzy@netflint.net `_, diff --git a/docs/using_asyncio.rst b/docs/using_asyncio.rst new file mode 100644 index 00000000..7f63d29d --- /dev/null +++ b/docs/using_asyncio.rst @@ -0,0 +1,125 @@ +.. _using_asyncio: + +============= +Using asyncio +============= + +Block on IQ sending +~~~~~~~~~~~~~~~~~~~ + +:meth:`.Iq.send` now accepts a ``coroutine`` parameter which, if ``True``, +will return a coroutine waiting for the IQ reply to be received. + +.. code-block:: python + + result = yield from iq.send(coroutine=True) + +XEP plugin integration +~~~~~~~~~~~~~~~~~~~~~~ + +Many XEP plugins have been modified to accept this ``coroutine`` parameter as +well, so you can do things like: + +.. code-block:: python + + iq_info = yield from self.xmpp['xep_0030'].get_info(jid, coroutine=True) + + +Running the event loop +~~~~~~~~~~~~~~~~~~~~~~ + +:meth:`.XMLStream.process` is only a thin wrapper on top of +``loop.run_forever()`` (if ``timeout`` is provided then it will +only run for this amount of time). + +Therefore you can handle the event loop in any way you like +instead of using ``process()``. + + +Examples +~~~~~~~~ + +Blocking until the session is established +----------------------------------------- + +This code blocks until the XMPP session is fully established, which +can be useful to make sure external events aren’t triggering XMPP +callbacks while everything is not ready. + +.. code-block:: python + + import asyncio, slixmpp + + client = slixmpp.ClientXMPP('jid@example', 'password') + client.connected_event = asyncio.Event() + callback = lambda event: client.connected_event.set() + client.add_event_handler('session_start', callback) + client.connect() + loop.run_until_complete(event.wait()) + # do some other stuff before running the event loop, e.g. + # loop.run_until_complete(httpserver.init()) + client.process() + + +Use with other asyncio-based libraries +-------------------------------------- + +This code interfaces with aiohttp to retrieve two pages asynchronously +when the session is established, and then send the HTML content inside +a simple . + +.. code-block:: python + + import asyncio, aiohttp, slixmpp + + @asyncio.coroutine + def get_pythonorg(event): + req = yield from aiohttp.request('get', 'http://www.python.org') + text = yield from req.text + client.send_message(mto='jid2@example', mbody=text) + + @asyncio.coroutine + def get_asyncioorg(event): + req = yield from aiohttp.request('get', 'http://www.asyncio.org') + text = yield from req.text + client.send_message(mto='jid3@example', mbody=text) + + client = slixmpp.ClientXMPP('jid@example', 'password') + client.add_event_handler('session_start', get_pythonorg) + client.add_event_handler('session_start', get_asyncioorg) + client.connect() + client.process() + + +Blocking Iq +----------- + +This client checks (via XEP-0092) the software used by every entity it +receives a message from. After this, it sends a message to a specific +JID indicating its findings. + +.. code-block:: python + + import asyncio, slixmpp + + class ExampleClient(slixmpp.ClientXMPP): + def __init__(self, *args, **kwargs): + slixmpp.ClientXMPP.__init__(self, *args, **kwargs) + self.register_plugin('xep_0092') + self.add_event_handler('message', self.on_message) + + @asyncio.coroutine + def on_message(self, event): + # You should probably handle IqError and IqTimeout exceptions here + # but this is an example. + version = yield from self['xep_0092'].get_version(message['from'], + coroutine=True) + text = "%s sent me a message, he runs %s" % (message['from'], + version['software_version']['name']) + self.send_message(mto='master@example.tld', mbody=text) + + client = ExampleClient('jid@example', 'password') + client.connect() + client.process() + + diff --git a/examples/IoT_TestDevice.py b/examples/IoT_TestDevice.py index b1968175..5b72994c 100755 --- a/examples/IoT_TestDevice.py +++ b/examples/IoT_TestDevice.py @@ -11,20 +11,12 @@ See the file LICENSE for copying permission. """ -import os -import sys -# This can be used when you are in a test environment and need to make paths right -sys.path=['/Users/jocke/Dropbox/06_dev/Slixmpp']+sys.path - import logging -import unittest -import distutils.core -import datetime -from glob import glob -from os.path import splitext, basename, join as pjoin +from os.path import basename, join as pjoin from argparse import ArgumentParser from urllib import urlopen +from getpass import getpass import slixmpp from slixmpp.plugins.xep_0323.device import Device @@ -186,5 +178,5 @@ if __name__ == '__main__': logging.debug("ready ending") else: - print "noopp didn't happen" + print("noopp didn't happen") diff --git a/examples/disco_browser.py b/examples/disco_browser.py index 33134c99..796562f2 100755 --- a/examples/disco_browser.py +++ b/examples/disco_browser.py @@ -15,6 +15,7 @@ from argparse import ArgumentParser import slixmpp from slixmpp.exceptions import IqError, IqTimeout +from slixmpp.xmlstream.asyncio import asyncio class Disco(slixmpp.ClientXMPP): @@ -53,6 +54,7 @@ class Disco(slixmpp.ClientXMPP): # our roster. self.add_event_handler("session_start", self.start) + @asyncio.coroutine def start(self, event): """ Process the session_start event. @@ -79,17 +81,17 @@ class Disco(slixmpp.ClientXMPP): # received. Non-blocking options would be to listen # for the disco_info event, or passing a handler # function using the callback parameter. - info = self['xep_0030'].get_info(jid=self.target_jid, - node=self.target_node, - block=True) - elif self.get in self.items_types: + info = yield from self['xep_0030'].get_info(jid=self.target_jid, + node=self.target_node, + coroutine=True) + if self.get in self.items_types: # The same applies from above. Listen for the # disco_items event or pass a callback function # if you need to process a non-blocking request. - items = self['xep_0030'].get_items(jid=self.target_jid, - node=self.target_node, - block=True) - else: + items = yield from self['xep_0030'].get_items(jid=self.target_jid, + node=self.target_node, + coroutine=True) + if self.get not in self.info_types and self.get not in self.items_types: logging.error("Invalid disco request type.") return except IqError as e: @@ -143,7 +145,7 @@ if __name__ == '__main__': parser.add_argument("-p", "--password", dest="password", help="password to use") parser.add_argument("query", choices=["all", "info", "items", "identities", "features"]) - parser.add_argument("target-jid") + parser.add_argument("target_jid") parser.add_argument("node", nargs='?') args = parser.parse_args() diff --git a/examples/download_avatars.py b/examples/download_avatars.py index c6d8c59c..1d7d72f8 100755 --- a/examples/download_avatars.py +++ b/examples/download_avatars.py @@ -11,11 +11,11 @@ import logging from getpass import getpass -import threading from argparse import ArgumentParser import slixmpp from slixmpp.exceptions import XMPPError +from slixmpp import asyncio FILE_TYPES = { @@ -40,8 +40,14 @@ class AvatarDownloader(slixmpp.ClientXMPP): self.add_event_handler('avatar_metadata_publish', self.on_avatar) self.received = set() - self.presences_received = threading.Event() + self.presences_received = asyncio.Event() + self.roster_received = asyncio.Event() + def roster_received_cb(self, event): + self.roster_received.set() + self.presences_received.clear() + + @asyncio.coroutine def start(self, event): """ Process the session_start event. @@ -56,16 +62,20 @@ class AvatarDownloader(slixmpp.ClientXMPP): data. """ self.send_presence() - self.get_roster() + self.get_roster(callback=self.roster_received_cb) print('Waiting for presence updates...\n') - self.presences_received.wait(15) + yield from self.roster_received.wait() + print('Roster received') + yield from self.presences_received.wait() self.disconnect() + @asyncio.coroutine def on_vcard_avatar(self, pres): print("Received vCard avatar update from %s" % pres['from'].bare) try: - result = self['xep_0054'].get_vcard(pres['from'], cached=True) + result = yield from self['xep_0054'].get_vcard(pres['from'].bare, cached=True, + coroutine=True, timeout=5) except XMPPError: print("Error retrieving avatar for %s" % pres['from']) return @@ -76,16 +86,18 @@ class AvatarDownloader(slixmpp.ClientXMPP): pres['from'].bare, pres['vcard_temp_update']['photo'], filetype) - with open(filename, 'w+') as img: + with open(filename, 'wb+') as img: img.write(avatar['BINVAL']) + @asyncio.coroutine def on_avatar(self, msg): print("Received avatar update from %s" % msg['from']) metadata = msg['pubsub_event']['items']['item']['avatar_metadata'] for info in metadata['items']: if not info['url']: try: - result = self['xep_0084'].retrieve_avatar(msg['from'], info['id']) + result = yield from self['xep_0084'].retrieve_avatar(msg['from'].bare, info['id'], + coroutine=True, timeout=5) except XMPPError: print("Error retrieving avatar for %s" % msg['from']) return @@ -94,7 +106,7 @@ class AvatarDownloader(slixmpp.ClientXMPP): filetype = FILE_TYPES.get(metadata['type'], 'png') filename = 'avatar_%s_%s.%s' % (msg['from'].bare, info['id'], filetype) - with open(filename, 'w+') as img: + with open(filename, 'wb+') as img: img.write(avatar['value']) else: # We could retrieve the avatar via HTTP, etc here instead. @@ -105,6 +117,7 @@ class AvatarDownloader(slixmpp.ClientXMPP): Wait to receive updates from all roster contacts. """ self.received.add(pres['from'].bare) + print((len(self.received), len(self.client_roster.keys()))) if len(self.received) >= len(self.client_roster.keys()): self.presences_received.set() else: diff --git a/examples/migrate_roster.py b/examples/migrate_roster.py index bd3bf7e6..e6c66f2e 100755 --- a/examples/migrate_roster.py +++ b/examples/migrate_roster.py @@ -103,5 +103,5 @@ def on_session2(event): new_xmpp.disconnect() new_xmpp.add_event_handler('session_start', on_session2) -if new_xmpp.connect(): - new_xmpp.process(block=True) +new_xmpp.connect() +new_xmpp.process() diff --git a/examples/ping.py b/examples/ping.py index 06b03c51..39118ac4 100755 --- a/examples/ping.py +++ b/examples/ping.py @@ -12,6 +12,8 @@ import logging from getpass import getpass from argparse import ArgumentParser +from slixmpp.exceptions import IqError, IqTimeout +from slixmpp import asyncio import slixmpp @@ -36,6 +38,7 @@ class PingTest(slixmpp.ClientXMPP): # our roster. self.add_event_handler("session_start", self.start) + @asyncio.coroutine def start(self, event): """ Process the session_start event. @@ -53,8 +56,8 @@ class PingTest(slixmpp.ClientXMPP): self.get_roster() try: - rtt = self['xep_0199'].ping(self.pingjid, - timeout=10) + rtt = yield from self['xep_0199'].ping(self.pingjid, + timeout=10) logging.info("Success! RTT: %s", rtt) except IqError as e: logging.info("Error pinging %s: %s", @@ -78,8 +81,7 @@ if __name__ == '__main__': action="store_const", dest="loglevel", const=logging.DEBUG, default=logging.INFO) parser.add_argument("-t", "--pingto", help="set jid to ping", - action="store", type="string", dest="pingjid", - default=None) + dest="pingjid", default=None) # JID and password options. parser.add_argument("-j", "--jid", dest="jid", diff --git a/examples/pubsub_client.py b/examples/pubsub_client.py index fd215efd..b379af95 100755 --- a/examples/pubsub_client.py +++ b/examples/pubsub_client.py @@ -7,7 +7,13 @@ from argparse import ArgumentParser import slixmpp from slixmpp.xmlstream import ET, tostring +from slixmpp.xmlstream.asyncio import asyncio +def make_callback(): + future = asyncio.Future() + def callback(result): + future.set_result(result) + return future, callback class PubsubClient(slixmpp.ClientXMPP): @@ -41,8 +47,10 @@ class PubsubClient(slixmpp.ClientXMPP): self.disconnect() def nodes(self): + future, callback = make_callback() try: - result = self['xep_0060'].get_nodes(self.pubsub_server, self.node) + self['xep_0060'].get_nodes(self.pubsub_server, self.node, callback=callback) + result = yield from future for item in result['disco_items']['items']: print(' - %s' % str(item)) except: @@ -63,16 +71,20 @@ class PubsubClient(slixmpp.ClientXMPP): def publish(self): payload = ET.fromstring("%s" % self.data) + future, callback = make_callback() try: - result = self['xep_0060'].publish(self.pubsub_server, self.node, payload=payload) + self['xep_0060'].publish(self.pubsub_server, self.node, payload=payload, callback=callback) + result = yield from future id = result['pubsub']['publish']['item']['id'] print('Published at item id: %s' % id) except: logging.error('Could not publish to: %s' % self.node) def get(self): + future, callback = make_callback() try: - result = self['xep_0060'].get_item(self.pubsub_server, self.node, self.data) + self['xep_0060'].get_item(self.pubsub_server, self.node, self.data, callback=callback) + result = yield from future for item in result['pubsub']['items']['substanzas']: print('Retrieved item %s: %s' % (item['id'], tostring(item['payload']))) except: @@ -80,28 +92,28 @@ class PubsubClient(slixmpp.ClientXMPP): def retract(self): try: - result = self['xep_0060'].retract(self.pubsub_server, self.node, self.data) + self['xep_0060'].retract(self.pubsub_server, self.node, self.data) print('Retracted item %s from node %s' % (self.data, self.node)) except: logging.error('Could not retract item %s from node %s' % (self.data, self.node)) def purge(self): try: - result = self['xep_0060'].purge(self.pubsub_server, self.node) + self['xep_0060'].purge(self.pubsub_server, self.node) print('Purged all items from node %s' % self.node) except: logging.error('Could not purge items from node %s' % self.node) def subscribe(self): try: - result = self['xep_0060'].subscribe(self.pubsub_server, self.node) + self['xep_0060'].subscribe(self.pubsub_server, self.node) print('Subscribed %s to node %s' % (self.boundjid.bare, self.node)) except: logging.error('Could not subscribe %s to node %s' % (self.boundjid.bare, self.node)) def unsubscribe(self): try: - result = self['xep_0060'].unsubscribe(self.pubsub_server, self.node) + self['xep_0060'].unsubscribe(self.pubsub_server, self.node) print('Unsubscribed %s from node %s' % (self.boundjid.bare, self.node)) except: logging.error('Could not unsubscribe %s from node %s' % (self.boundjid.bare, self.node)) diff --git a/examples/register_account.py b/examples/register_account.py index 9d6e3b8f..1cfc8c72 100755 --- a/examples/register_account.py +++ b/examples/register_account.py @@ -90,7 +90,7 @@ class RegisterBot(slixmpp.ClientXMPP): resp['register']['password'] = self.password try: - resp.send() + yield from resp.send_coroutine() logging.info("Account created for %s!" % self.boundjid) except IqError as e: logging.error("Could not register account: %s" % diff --git a/examples/roster_browser.py b/examples/roster_browser.py index 74d2839a..4d07de11 100755 --- a/examples/roster_browser.py +++ b/examples/roster_browser.py @@ -11,11 +11,11 @@ import logging from getpass import getpass -import threading from argparse import ArgumentParser import slixmpp from slixmpp.exceptions import IqError, IqTimeout +from slixmpp.xmlstream.asyncio import asyncio class RosterBrowser(slixmpp.ClientXMPP): @@ -36,8 +36,9 @@ class RosterBrowser(slixmpp.ClientXMPP): self.add_event_handler("changed_status", self.wait_for_presences) self.received = set() - self.presences_received = threading.Event() + self.presences_received = asyncio.Event() + @asyncio.coroutine def start(self, event): """ Process the session_start event. @@ -51,8 +52,12 @@ class RosterBrowser(slixmpp.ClientXMPP): event does not provide any additional data. """ + future = asyncio.Future() + def callback(result): + future.set_result(None) try: - self.get_roster() + self.get_roster(callback=callback) + yield from future except IqError as err: print('Error: %' % err.iq['error']['condition']) except IqTimeout: @@ -61,7 +66,7 @@ class RosterBrowser(slixmpp.ClientXMPP): print('Waiting for presence updates...\n') - self.presences_received.wait(5) + yield from asyncio.sleep(10) print('Roster for %s' % self.boundjid.bare) groups = self.client_roster.groups() diff --git a/examples/rpc_async.py b/examples/rpc_async.py index 6798d63c..f773a8d1 100755 --- a/examples/rpc_async.py +++ b/examples/rpc_async.py @@ -20,7 +20,7 @@ class Boomerang(Endpoint): @remote def throw(self): - print "Duck!" + print("Duck!") diff --git a/examples/set_avatar.py b/examples/set_avatar.py index 9a050e5a..5805d18a 100755 --- a/examples/set_avatar.py +++ b/examples/set_avatar.py @@ -18,7 +18,7 @@ from argparse import ArgumentParser import slixmpp from slixmpp.exceptions import XMPPError - +from slixmpp import asyncio class AvatarSetter(slixmpp.ClientXMPP): @@ -33,6 +33,7 @@ class AvatarSetter(slixmpp.ClientXMPP): self.filepath = filepath + @asyncio.coroutine def start(self, event): """ Process the session_start event. @@ -51,7 +52,7 @@ class AvatarSetter(slixmpp.ClientXMPP): avatar_file = None try: - avatar_file = open(os.path.expanduser(self.filepath)) + avatar_file = open(os.path.expanduser(self.filepath), 'rb') except IOError: print('Could not find file: %s' % self.filepath) return self.disconnect() @@ -65,32 +66,31 @@ class AvatarSetter(slixmpp.ClientXMPP): avatar_file.close() used_xep84 = False - try: - print('Publish XEP-0084 avatar data') - self['xep_0084'].publish_avatar(avatar) - used_xep84 = True - except XMPPError: + + print('Publish XEP-0084 avatar data') + result = yield from self['xep_0084'].publish_avatar(avatar, coroutine=True) + if isinstance(result, XMPPError): print('Could not publish XEP-0084 avatar') + else: + used_xep84 = True - try: - print('Update vCard with avatar') - self['xep_0153'].set_avatar(avatar=avatar, mtype=avatar_type) - except XMPPError: + print('Update vCard with avatar') + result = yield from self['xep_0153'].set_avatar(avatar=avatar, mtype=avatar_type, coroutine=True) + if isinstance(result, XMPPError): print('Could not set vCard avatar') if used_xep84: - try: - print('Advertise XEP-0084 avatar metadata') - self['xep_0084'].publish_avatar_metadata([ - {'id': avatar_id, - 'type': avatar_type, - 'bytes': avatar_bytes} - # We could advertise multiple avatars to provide - # options in image type, source (HTTP vs pubsub), - # size, etc. - # {'id': ....} - ]) - except XMPPError: + print('Advertise XEP-0084 avatar metadata') + result = yield from self['xep_0084'].publish_avatar_metadata([ + {'id': avatar_id, + 'type': avatar_type, + 'bytes': avatar_bytes} + # We could advertise multiple avatars to provide + # options in image type, source (HTTP vs pubsub), + # size, etc. + # {'id': ....} + ], coroutine=True) + if isinstance(result, XMPPError): print('Could not publish XEP-0084 metadata') print('Wait for presence updates to propagate...') diff --git a/slixmpp/stanza/iq.py b/slixmpp/stanza/iq.py index bb46aecc..f7b222e6 100644 --- a/slixmpp/stanza/iq.py +++ b/slixmpp/stanza/iq.py @@ -32,6 +32,9 @@ class Iq(RootStanza): as a carrier stanza for an application-specific protocol instead. Example Stanzas: + + .. code-block:: xml + @@ -47,20 +50,9 @@ class Iq(RootStanza): Stanza Interface: - query -- The namespace of the element if one exists. - + - **query**: The namespace of the element if one exists. Attributes: - types -- May be one of: get, set, result, or error. - - Methods: - __init__ -- Overrides StanzaBase.__init__. - unhandled -- Send error if there are no handlers. - set_payload -- Overrides StanzaBase.set_payload. - set_query -- Add or modify a element. - get_query -- Return the namespace of the element. - del_query -- Remove the element. - reply -- Overrides StanzaBase.reply - send -- Overrides StanzaBase.send + - **types**: May be one of: get, set, result, or error. """ namespace = 'jabber:client' @@ -98,8 +90,9 @@ class Iq(RootStanza): """ Set the XML contents of the stanza. - Arguments: - value -- An XML object to use as the stanza's contents + :param value: An XML object or a list of XML objects to use as the + stanza's contents + :type value: list or XML object """ self.clear() StanzaBase.set_payload(self, value) @@ -111,8 +104,7 @@ class Iq(RootStanza): Query elements are differentiated by their namespace. - Arguments: - value -- The namespace of the element. + :param str value: The namespace of the element. """ query = self.xml.find("{%s}query" % value) if query is None and value: @@ -126,7 +118,9 @@ class Iq(RootStanza): return self def get_query(self): - """Return the namespace of the element.""" + """Return the namespace of the element. + + :rtype: str""" for child in self.xml: if child.tag.endswith('query'): ns = child.tag.split('}')[0] @@ -144,16 +138,15 @@ class Iq(RootStanza): def reply(self, clear=True): """ - Send a reply stanza. + Create a new stanza replying to ``self``. Overrides StanzaBase.reply Sets the 'type' to 'result' in addition to the default StanzaBase.reply behavior. - Arguments: - clear -- Indicates if existing content should be - removed before replying. Defaults to True. + :param bool clear: Indicates if existing content should be + removed before replying. Defaults to True. """ new_iq = StanzaBase.reply(self, clear=clear) new_iq['type'] = 'result' @@ -168,10 +161,8 @@ class Iq(RootStanza): Overrides StanzaBase.send - Arguments: - - timeout -- The length of time (in seconds) to wait for a - response before an IqTimeout is raised + :param int timeout: The length of time (in seconds) to wait for a + response before an IqTimeout is raised """ future = asyncio.Future() @@ -216,20 +207,19 @@ class Iq(RootStanza): Overrides StanzaBase.send - Arguments: - - callback -- Optional reference to a stream handler - function. Will be executed when a reply stanza is - received. - timeout -- The length of time (in seconds) to wait for a - response before the timeout_callback is called, - instead of the regular callback - timeout_callback -- Optional reference to a stream handler - function. Will be executed when the timeout expires - before a response has been received with the - originally-sent IQ stanza. - coroutine -- This function will return a coroutine if this argument - is True. + :param function callback: Optional reference to a stream handler + function. Will be executed when a reply + stanza is received. + :param int timeout: The length of time (in seconds) to wait for a + response before the timeout_callback is called, + instead of the regular callback + :param function timeout_callback: Optional reference to a stream handler + function. Will be executed when the + timeout expires before a response has + been received for the originally-sent + IQ stanza. + :param bool coroutine: This function will return a coroutine if this + argument is True. """ if self.stream.session_bind_event.is_set(): matcher = MatchIDSender({ diff --git a/slixmpp/stanza/message.py b/slixmpp/stanza/message.py index 7f9e90f5..cbb170fa 100644 --- a/slixmpp/stanza/message.py +++ b/slixmpp/stanza/message.py @@ -23,6 +23,9 @@ class Message(RootStanza): an error response. Example stanzas: + + .. code-block:: xml + Hi! @@ -32,26 +35,13 @@ class Message(RootStanza): Stanza Interface: - body -- The main contents of the message. - subject -- An optional description of the message's contents. - mucroom -- (Read-only) The name of the MUC room that sent the message. - mucnick -- (Read-only) The MUC nickname of message's sender. + - **body**: The main contents of the message. + - **subject**: An optional description of the message's contents. + - **mucroom**: (Read-only) The name of the MUC room that sent the message. + - **mucnick**: (Read-only) The MUC nickname of message's sender. Attributes: - types -- May be one of: normal, chat, headline, groupchat, or error. - - Methods: - setup -- Overrides StanzaBase.setup. - chat -- Set the message type to 'chat'. - normal -- Set the message type to 'normal'. - reply -- Overrides StanzaBase.reply - get_type -- Overrides StanzaBase interface - get_mucroom -- Return the name of the MUC room of the message. - set_mucroom -- Dummy method to prevent assignment. - del_mucroom -- Dummy method to prevent deletion. - get_mucnick -- Return the MUC nickname of the message's sender. - set_mucnick -- Dummy method to prevent assignment. - del_mucnick -- Dummy method to prevent deletion. + - **types**: May be one of: normal, chat, headline, groupchat, or error. """ name = 'message' @@ -81,18 +71,25 @@ class Message(RootStanza): Overrides default stanza interface behavior. Returns 'normal' if no type attribute is present. + + :rtype: str """ return self._get_attr('type', 'normal') def get_parent_thread(self): - """Return the message thread's parent thread.""" + """Return the message thread's parent thread. + + :rtype: str + """ thread = self.xml.find('{%s}thread' % self.namespace) if thread is not None: return thread.attrib.get('parent', '') return '' def set_parent_thread(self, value): - """Add or change the message thread's parent thread.""" + """Add or change the message thread's parent thread. + + :param str value: identifier of the thread""" thread = self.xml.find('{%s}thread' % self.namespace) if value: if thread is None: @@ -128,10 +125,11 @@ class Message(RootStanza): Sets proper 'to' attribute if the message is from a MUC, and adds a message body if one is given. - Arguments: - body -- Optional text content for the message. - clear -- Indicates if existing content should be removed - before replying. Defaults to True. + :param str body: Optional text content for the message. + :param bool clear: Indicates if existing content should be removed + before replying. Defaults to True. + + :rtype: :class:`~.Message` """ new_message = StanzaBase.reply(self, clear) @@ -152,6 +150,8 @@ class Message(RootStanza): Return the name of the MUC room where the message originated. Read-only stanza interface. + + :rtype: str """ if self['type'] == 'groupchat': return self['from'].bare @@ -163,6 +163,8 @@ class Message(RootStanza): Return the nickname of the MUC user that sent the message. Read-only stanza interface. + + :rtype: str """ if self['type'] == 'groupchat': return self['from'].resource diff --git a/slixmpp/stanza/presence.py b/slixmpp/stanza/presence.py index e1b8c0bc..1e8a940e 100644 --- a/slixmpp/stanza/presence.py +++ b/slixmpp/stanza/presence.py @@ -27,6 +27,9 @@ class Presence(RootStanza): to help keep the network running smoothly. Example stanzas: + + .. code-block:: xml + @@ -40,24 +43,14 @@ class Presence(RootStanza): Stanza Interface: - priority -- A value used by servers to determine message routing. - show -- The type of status, such as away or available for chat. - status -- Custom, human readable status message. + - **priority**: A value used by servers to determine message routing. + - **show**: The type of status, such as away or available for chat. + - **status**: Custom, human readable status message. Attributes: - types -- One of: available, unavailable, error, probe, - subscribe, subscribed, unsubscribe, - and unsubscribed. - showtypes -- One of: away, chat, dnd, and xa. - - Methods: - setup -- Overrides StanzaBase.setup - reply -- Overrides StanzaBase.reply - set_show -- Set the value of the element. - get_type -- Get the value of the type attribute or element. - set_type -- Set the value of the type attribute or element. - get_priority -- Get the value of the element. - set_priority -- Set the value of the element. + - **types**: One of: available, unavailable, error, probe, + subscribe, subscribed, unsubscribe, and unsubscribed. + - **showtypes**: One of: away, chat, dnd, and xa. """ name = 'presence' @@ -93,8 +86,7 @@ class Presence(RootStanza): """ Set the value of the element. - Arguments: - show -- Must be one of: away, chat, dnd, or xa. + :param str show: Must be one of: away, chat, dnd, or xa. """ if show is None: self._del_sub('show') @@ -119,8 +111,7 @@ class Presence(RootStanza): Set the type attribute's value, and the element if applicable. - Arguments: - value -- Must be in either self.types or self.showtypes. + :param str value: Must be in either self.types or self.showtypes. """ if value in self.types: self['show'] = None @@ -146,14 +137,15 @@ class Presence(RootStanza): Bot clients should typically use a priority of 0 if the same JID is used elsewhere by a human-interacting client. - Arguments: - value -- An integer value greater than or equal to 0. + :param int value: An integer value greater than or equal to 0. """ self._set_sub_text('priority', text=str(value)) def get_priority(self): """ Return the value of the element as an integer. + + :rtype: int """ p = self._get_sub_text('priority') if not p: @@ -166,13 +158,12 @@ class Presence(RootStanza): def reply(self, clear=True): """ - Set the appropriate presence reply type. + Create a new reply stanza from ``self``. Overrides StanzaBase.reply. - Arguments: - clear -- Indicates if the stanza contents should be removed - before replying. Defaults to True. + :param bool clear: Indicates if the stanza contents should be removed + before replying. Defaults to True. """ new_presence = StanzaBase.reply(self, clear) if self['type'] == 'unsubscribe': diff --git a/slixmpp/xmlstream/stanzabase.py b/slixmpp/xmlstream/stanzabase.py index 392fd55b..10c29782 100644 --- a/slixmpp/xmlstream/stanzabase.py +++ b/slixmpp/xmlstream/stanzabase.py @@ -1474,7 +1474,7 @@ class StanzaBase(ElementBase): Only type values contained in :attr:`types` are accepted. - :param string value: One of the values contained in :attr:`types` + :param str value: One of the values contained in :attr:`types` """ if value in self.types: self.xml.attrib['type'] = value @@ -1499,8 +1499,8 @@ class StanzaBase(ElementBase): def set_from(self, value): """Set the 'from' attribute of the stanza. - Arguments: - from -- A string or JID object representing the sender's JID. + :param from: A string or JID object representing the sender's JID. + :type from: str or :class:`.JID` """ return self._set_attr('from', str(value)) -- cgit v1.2.3