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
This commit is contained in:
mathieui 2015-02-24 18:58:40 +01:00
parent e112e86475
commit c66a4d4097
No known key found for this signature in database
GPG Key ID: C59F84CEEFD616E3
36 changed files with 498 additions and 401 deletions

8
docs/api/stanza/iq.rst Normal file
View File

@ -0,0 +1,8 @@
IQ Stanza
=========
.. module:: slixmpp.stanza
.. autoclass:: Iq
:members:

View File

@ -0,0 +1,7 @@
Message Stanza
==============
.. module:: slixmpp.stanza
.. autoclass:: Message
:members:

View File

@ -0,0 +1,8 @@
Presence Stanza
===============
.. module:: slixmpp.stanza
.. autoclass:: Presence
:members:

View File

@ -0,0 +1,8 @@
Root Stanza
===========
.. module:: slixmpp.stanza.rootstanza
.. autoclass:: RootStanza
:members:

View File

@ -1,12 +0,0 @@
.. module:: slixmpp.xmlstream.filesocket
.. _filesocket:
Python 2.6 File Socket Shims
============================
.. autoclass:: FileSocket
:members:
.. autoclass:: Socket26
:members:

View File

@ -10,15 +10,19 @@ The Basic Handler
Callback Callback
-------- --------
.. module:: slixmpp.xmlstream.handler.callback .. module:: slixmpp.xmlstream.handler
.. autoclass:: Callback .. autoclass:: Callback
:members: :members:
CoroutineCallback
-----------------
.. autoclass:: CoroutineCallback
:members:
Waiter Waiter
------ ------
.. module:: slixmpp.xmlstream.handler.waiter
.. autoclass:: Waiter .. autoclass:: Waiter
:members: :members:

View File

@ -1,7 +1,7 @@
Jabber IDs (JID) Jabber IDs (JID)
================= =================
.. module:: slixmpp.xmlstream.jid .. module:: slixmpp.jid
.. autoclass:: JID .. autoclass:: JID
:members: :members:

View File

@ -1,11 +0,0 @@
=========
Scheduler
=========
.. module:: slixmpp.xmlstream.scheduler
.. autoclass:: Task
:members:
.. autoclass:: Scheduler
:members:

View File

@ -61,8 +61,8 @@ interacting with a given :term:`stanza` a :term:`stanza object`.
To make dealing with more complicated and nested :term:`stanzas <stanza>` To make dealing with more complicated and nested :term:`stanzas <stanza>`
or XML chunks easier, :term:`stanza objects <stanza object>` can be or XML chunks easier, :term:`stanza objects <stanza object>` can be
composed in two ways: as iterable child objects or as plugins. Iterable composed in two ways: as iterable child objects or as plugins. Iterable
child stanzas, or :term:`substanzas`, are accessible through a special child stanzas, or :term:`substanzas <substanza>`, are accessible through a
``'substanzas'`` interface. This option is useful for stanzas which special ``'substanzas'`` interface. This option is useful for stanzas which
may contain more than one of the same kind of element. When there is 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, 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 a parent stanza object delegates one of its XML child elements to the

View File

@ -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 you create a :class:`~slixmpp.stanza.message.Message` instance and dump
it to the terminal, the ``jabber:client`` namespace will appear. it to the terminal, the ``jabber:client`` namespace will appear.
.. autofunction:: tostring .. autofunction:: slixmpp.xmlstream.tostring
Escaping Special Characters 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 size of escaped text or for when other XMPP processing agents do not
undertand these entities. undertand these entities.
.. autofunction:: xml_escape ..
autofunction:: xml_escape

View File

@ -24,21 +24,20 @@ patterns is received; these callbacks are also referred to as :term:`stream
handlers <stream handler>`. The class also provides a basic eventing system handlers <stream handler>`. The class also provides a basic eventing system
which can be triggered either manually or on a timed schedule. which can be triggered either manually or on a timed schedule.
The Main Threads The event loop
~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~
:class:`~slixmpp.xmlstream.xmlstream.XMLStream` instances run using at :class:`~slixmpp.xmlstream.xmlstream.XMLStream` instances inherit the
least three background threads: the send thread, the read thread, and the :class:`asyncio.BaseProtocol` class, and therefore do not have to handle
scheduler thread. The send thread is in charge of monitoring the send queue reads and writes directly, but receive data through
and writing text to the outgoing XML stream. The read thread pulls text off :meth:`~slixmpp.xmlstream.xmlstream.XMLStream.data_received` and write
of the incoming XML stream and stores the results in an event queue. The data in the socket transport.
scheduler thread is used to emit events after a given period of time.
Additionally, the main event processing loop may be executed in its Upon receiving data, :term:`stream handlers <stream handler>` are run
own thread if Slixmpp is being used in the background for another immediately, except if they are coroutines, in which case they are
application. scheduled using :meth:`asyncio.async`.
Short-lived threads may also be spawned as requested for threaded :term:`Event handlers <event handler>` (which are called inside
:term:`event handlers <event handler>`. :term:`stream handlers <stream handler>`) work the same way.
How XML Text is Turned into Action How XML Text is Turned into Action
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
@ -53,7 +52,7 @@ when this bit of XML is received (with an assumed namespace of
</message> </message>
1. **Convert XML strings into objects.** #. **Convert XML strings into objects.**
Incoming text is parsed and converted into XML objects (using Incoming text is parsed and converted into XML objects (using
ElementTree) which are then wrapped into what are referred to as 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 ``{jabber:client}message`` is associated with the class
:class:`~slixmpp.stanza.Message`. :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 These objects are then compared against the stored patterns associated
with the registered callback handlers. For each match, a copy of the with the registered callback handlers.
: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 Each handler matching our :term:`stanza object` is then added to a list.
:meth:`BaseXMPP._handle_message` to create the tuple::
('stanza', stanza_obj, handler) #. **Processing callbacks**
3. **Process the event queue.** 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.
The event queue is the heart of Slixmpp. Nearly every action that #. **Raise Custom Events**
takes place is first inserted into this queue, whether that be received
stanzas, custom events, or scheduled events.
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**
Since a :term:`stream handler` shouldn't block, if extensive processing Since a :term:`stream handler` shouldn't block, if extensive processing
for a stanza is required (such as needing to send and receive an for a stanza is required (such as needing to send and receive an
:class:`~slixmpp.stanza.Iq` stanza), then custom events must be used. :class:`~slixmpp.stanza.Iq` stanza), then custom events must be used.
These events are not explicitly tied to the incoming XML stream and may 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 be raised at any time.
own thread.
When the event is raised, a copy of the stanza is created for each In contrast to :term:`stream handlers <stream handler>`, these functions
handler registered for the event. In contrast to :term:`stream handlers are referred to as :term:`event handlers <event handler>`.
<stream handler>`, these functions are referred to as :term:`event
handlers <event handler>`. Each stanza/handler pair is then put into the
event queue.
The code for :meth:`BaseXMPP._handle_message` follows this pattern, and The code for :meth:`BaseXMPP._handle_message` follows this pattern, and
raises a ``'message'`` event:: raises a ``'message'`` event
self.event('message', msg) .. code-block:: python
The event call then places the message object back into the event queue self.event('message', msg)
paired with an :term:`event handler`::
('event', 'message', msg_copy1, custom_event_handler_1) #. **Process Custom Events**
('event', 'message', msg_copy2, custom_evetn_handler_2)
5. **Process Custom Events** The :term:`event handlers <event handler>` are then executed, passing
the stanza as the only argument.
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.
.. note:: .. note::
Events may be raised without needing :term:`stanza objects <stanza object>`. Events may be raised without needing :term:`stanza objects <stanza object>`.
@ -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 Finally, after a long trek, our message is handed off to the user's
custom handler in order to do awesome stuff:: custom handler in order to do awesome stuff::
msg.reply() reply = msg.reply()
msg['body'] = "Hey! This is awesome!" reply['body'] = "Hey! This is awesome!"
msg.send() reply.send()
.. index:: BaseXMPP, XMLStream .. index:: BaseXMPP, XMLStream

View File

@ -105,7 +105,7 @@ html_theme = 'haiku'
# The name for this set of Sphinx documents. If None, it defaults to # The name for this set of Sphinx documents. If None, it defaults to
# "<project> v<release> documentation". # "<project> v<release> documentation".
html_title = 'Slixmpp' html_title = 'slixmpp'
# A shorter title for the navigation bar. Default is the same as html_title. # A shorter title for the navigation bar. Default is the same as html_title.
html_short_title = '%s Documentation' % release html_short_title = '%s Documentation' % release
@ -219,4 +219,4 @@ man_pages = [
[u'Nathan Fritz, Lance Stout'], 1) [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')}

View File

@ -634,8 +634,9 @@ with some additional registration fields implemented.
if self.backend.register(iq['from'].bare, iq['register']): if self.backend.register(iq['from'].bare, iq['register']):
# Successful registration # Successful registration
self.xmpp.event('registered_user', iq) self.xmpp.event('registered_user', iq)
iq.reply().set_payload(iq['register'].xml) reply = iq.reply()
iq.send() reply.set_payload(iq['register'].xml)
reply.send()
else: else:
# Conflicting registration # Conflicting registration
self._sendError(iq, '409', 'cancel', 'conflict', self._sendError(iq, '409', 'cancel', 'conflict',
@ -666,14 +667,16 @@ with some additional registration fields implemented.
# Add a blank field # Add a blank field
reg.addField(field) reg.addField(field)
iq.reply().set_payload(reg.xml) reply = iq.reply()
iq.send() reply.set_payload(reg.xml)
reply.send()
def _sendError(self, iq, code, error_type, name, text=''): def _sendError(self, iq, code, error_type, name, text=''):
iq.reply().set_payload(iq['register'].xml) reply = iq.reply()
iq.error() reply.set_payload(iq['register'].xml)
iq['error']['code'] = code reply.error()
iq['error']['type'] = error_type reply['error']['code'] = code
iq['error']['condition'] = name reply['error']['type'] = error_type
iq['error']['text'] = text reply['error']['condition'] = name
iq.send() reply['error']['text'] = text
reply.send()

47
docs/differences.rst Normal file
View File

@ -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` objects :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 doesnt work but should, please report it.

View File

@ -7,19 +7,11 @@ Create and Run a Server Component
.. note:: .. note::
If you have any issues working through this quickstart guide If you have any issues working through this quickstart guide
or the other tutorials here, please either send a message to the join the chat room at `slixmpp@muc.poez.io
`mailing list <http://groups.google.com/group/slixmpp-discussion>`_ <xmpp:slixmpp@muc.poez.io?join>`_.
or join the chat room at `sleek@conference.jabber.org
<xmpp:sleek@conference.jabber.org?join>`_.
If you have not yet installed Slixmpp, do so now by either checking out a version If you have not yet installed Slixmpp, do so now by either checking out a version
from `Github <http://github.com/fritzy/Slixmpp>`_, or installing it using ``pip`` with `Git <http://git.poez.io/slixmpp>`_.
or ``easy_install``.
.. code-block:: sh
pip install slixmpp # Or: easy_install slixmpp
Many XMPP applications eventually graduate to requiring to run as a server Many XMPP applications eventually graduate to requiring to run as a server
component in order to meet scalability requirements. To demonstrate how to component in order to meet scalability requirements. To demonstrate how to

View File

@ -7,19 +7,11 @@ Slixmpp Quickstart - Echo Bot
.. note:: .. note::
If you have any issues working through this quickstart guide If you have any issues working through this quickstart guide
or the other tutorials here, please either send a message to the join the chat room at `slixmpp@muc.poez.io
`mailing list <http://groups.google.com/group/slixmpp-discussion>`_ <xmpp:slixmpp@muc.poez.io?join>`_.
or join the chat room at `sleek@conference.jabber.org
<xmpp:sleek@conference.jabber.org?join>`_.
If you have not yet installed Slixmpp, do so now by either checking out a version If you have not yet installed Slixmpp, do so now by either checking out a version
from `Github <http://github.com/fritzy/Slixmpp>`_, or installing it using ``pip`` with `Git <http://git.poez.io/slixmpp>`_.
or ``easy_install``.
.. code-block:: sh
pip install slixmpp # Or: easy_install slixmpp
As a basic starting project, we will create an echo bot which will reply to any 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 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 -*- # -*- coding: utf-8 -*-
import sys import sys
import asyncio
import logging import logging
import getpass import getpass
from optparse import OptionParser 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''' '''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 Creating the EchoBot Class
-------------------------- --------------------------
@ -313,9 +288,9 @@ the ``EchoBot.__init__`` method instead.
xmpp.ssl_version = ssl.PROTOCOL_SSLv3 xmpp.ssl_version = ssl.PROTOCOL_SSLv3
Now we're ready to connect and begin echoing messages. If you have the package 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 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 connect to the hostname used by the JID, unless an address tuple is supplied
to :meth:`slixmpp.clientxmpp.ClientXMPP`. to :meth:`slixmpp.clientxmpp.ClientXMPP`.
@ -330,22 +305,6 @@ to :meth:`slixmpp.clientxmpp.ClientXMPP`.
else: else:
print('Unable to connect') 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` 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 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 the :meth:`slixmpp.plugins.base.BasePlugin.post_init` method on all registered plugins. By

View File

@ -7,19 +7,11 @@ Mulit-User Chat (MUC) Bot
.. note:: .. note::
If you have any issues working through this quickstart guide If you have any issues working through this quickstart guide
or the other tutorials here, please either send a message to the join the chat room at `slixmpp@muc.poez.io
`mailing list <http://groups.google.com/group/slixmpp-discussion>`_ <xmpp:slixmpp@muc.poez.io?join>`_.
or join the chat room at `sleek@conference.jabber.org
<xmpp:sleek@conference.jabber.org?join>`_.
If you have not yet installed Slixmpp, do so now by either checking out a version If you have not yet installed Slixmpp, do so now by either checking out a version
from `Github <http://github.com/fritzy/Slixmpp>`_, or installing it using ``pip`` from `Git <http://git.poez.io/slixmpp>`_.
or ``easy_install``.
.. code-block:: sh
pip install slixmpp # Or: easy_install slixmpp
Now that you've got the basic gist of using Slixmpp by following the 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 echobot example (:ref:`echobot`), we can use one of the bundled plugins

View File

@ -7,10 +7,8 @@ Enable HTTP Proxy Support
.. note:: .. note::
If you have any issues working through this quickstart guide If you have any issues working through this quickstart guide
or the other tutorials here, please either send a message to the join the chat room at `slixmpp@muc.poez.io
`mailing list <http://groups.google.com/group/slixmpp-discussion>`_ <xmpp:slixmpp@muc.poez.io?join>`_.
or join the chat room at `sleek@conference.jabber.org
<xmpp:sleek@conference.jabber.org?join>`_.
In some instances, you may wish to route XMPP traffic through In some instances, you may wish to route XMPP traffic through
an HTTP proxy, probably to get around restrictive firewalls. an HTTP proxy, probably to get around restrictive firewalls.

View File

@ -4,10 +4,8 @@ Sign in, Send a Message, and Disconnect
.. note:: .. note::
If you have any issues working through this quickstart guide If you have any issues working through this quickstart guide
or the other tutorials here, please either send a message to the join the chat room at `slixmpp@muc.poez.io
`mailing list <http://groups.google.com/group/slixmpp-discussion>`_ <xmpp:slixmpp@muc.poez.io?join>`_.
or join the chat room at `sleek@conference.jabber.org
<xmpp:sleek@conference.jabber.org?join>`_.
A common use case for Slixmpp is to send one-off messages from 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 time to time. For example, one use case could be sending out a notice when

View File

@ -9,21 +9,20 @@ Glossary
stream handler stream handler
A callback function that accepts stanza objects pulled directly A callback function that accepts stanza objects pulled directly
from the XML stream. A stream handler is encapsulated in a from the XML stream. A stream handler is encapsulated in a
object that includes a :term:`Matcher` object, and which provides object that includes a :class:`Matcher <.MatcherBase>` object, and
additional semantics. For example, the ``Waiter`` handler wrapper which provides additional semantics. For example, the
blocks thread execution until a matching stanza is received. :class:`.Waiter` handler wrapper blocks thread execution until a
matching stanza is received.
event handler event handler
A callback function that responds to events raised by A callback function that responds to events raised by
``XMLStream.event``. An event handler may be marked as :meth:`.XMLStream.event`.
threaded, allowing it to execute outside of the main processing
loop.
stanza object stanza object
Informally may refer both to classes which extend ``ElementBase`` Informally may refer both to classes which extend :class:`.ElementBase`
or ``StanzaBase``, and to objects of such classes. 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. like interfaces which may be assigned to, read from, or deleted.
stanza plugin stanza plugin

View File

@ -3,38 +3,25 @@ Slixmpp
.. sidebar:: Get the Code .. sidebar:: Get the Code
.. code-block:: sh The latest source code for Slixmpp may be found on the `Git repo
<http://git.poez.io/slixmpp>`_. ::
pip install slixmpp git clone git://git.poez.io/slixmpp
The latest source code for Slixmpp may be found on `Github An XMPP chat room is available for discussing and getting help with slixmpp.
<http://github.com/fritzy/Slixmpp>`_. Releases can be found in the
``master`` branch, while the latest development version is in the
``develop`` branch.
**Latest Stable Release**
- `1.0 <http://github.com/fritzy/Slixmpp/zipball/1.0>`_
**Develop Releases**
- `Latest Develop Version <http://github.com/fritzy/Slixmpp/zipball/develop>`_
A mailing list and XMPP chat room are available for discussing and getting
help with Slixmpp.
**Mailing List**
`Slixmpp Discussion on Google Groups <http://groups.google.com/group/slixmpp-discussion>`_
**Chat** **Chat**
`sleek@conference.jabber.org <xmpp:sleek@conference.jabber.org?join>`_ `slixmpp@muc.poez.io <xmpp:slixmpp@muc.poez.io?join>`_
**Reporting bugs**
You can report bugs at http://dev.louiz.org/projects/slixmpp/issues.
Slixmpp is an :ref:`MIT licensed <license>` XMPP library for Python 2.6/3.1+, .. note::
and is featured in examples in slixmpp is a friendly fork of `SleekXMPP <https://github.com/fritzy/SleekXMPP>`_
`XMPP: The Definitive Guide <http://oreilly.com/catalog/9780596521271>`_ which goal is to use asyncio instead of threads to handle networking. See
by Kevin Smith, Remko Tronçon, and Peter Saint-Andre. If you've arrived :ref:`differences`.
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 <license>` XMPP library for Python 3.4+,
Slixmpp's design goals and philosphy are: 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 sensible defaults and appropriate abstractions. XML can be ugly to work
with, but it doesn't have to be that way. with, but it doesn't have to be that way.
Here's your first Slixmpp Bot: Here's your first Slixmpp Bot:
-------------------------------- --------------------------------
.. code-block:: python .. code-block:: python
import asyncio
import logging import logging
from slixmpp import ClientXMPP 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: # Here's how to access plugins once you've registered them:
# self['xep_0030'].add_feature('echo_demo') # 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): def session_start(self, event):
self.send_presence() self.send_presence()
self.get_roster() self.get_roster()
# Most get_*/set_* methods from plugins use Iq stanzas, which # Most get_*/set_* methods from plugins use Iq stanzas, which
# can generate IqError and IqTimeout exceptions # are sent asynchronously. You can almost always provide a
# # callback that will be executed when the reply is received.
# 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()
def message(self, msg): def message(self, msg):
if msg['type'] in ('chat', 'normal'): if msg['type'] in ('chat', 'normal'):
@ -121,9 +96,18 @@ Here's your first Slixmpp Bot:
xmpp = EchoBot('somejid@example.com', 'use_getpass') xmpp = EchoBot('somejid@example.com', 'use_getpass')
xmpp.connect() 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) Getting Started (with Examples)
------------------------------- -------------------------------
@ -145,7 +129,6 @@ Tutorials, FAQs, and How To Guides
.. toctree:: .. toctree::
:maxdepth: 1 :maxdepth: 1
faq
xeps xeps
xmpp_tdg xmpp_tdg
howto/stanzas howto/stanzas
@ -184,9 +167,7 @@ API Reference
api/xmlstream/handler api/xmlstream/handler
api/xmlstream/matcher api/xmlstream/matcher
api/xmlstream/xmlstream api/xmlstream/xmlstream
api/xmlstream/scheduler
api/xmlstream/tostring api/xmlstream/tostring
api/xmlstream/filesocket
Core Stanzas Core Stanzas
~~~~~~~~~~~~ ~~~~~~~~~~~~
@ -197,8 +178,6 @@ Core Stanzas
api/stanza/message api/stanza/message
api/stanza/presence api/stanza/presence
api/stanza/iq api/stanza/iq
api/stanza/error
api/stanza/stream_error
Plugins Plugins
~~~~~~~ ~~~~~~~
@ -220,8 +199,14 @@ Additional Info
* :ref:`modindex` * :ref:`modindex`
* :ref:`search` * :ref:`search`
Credits SleekXMPP Credits
------- -----------------
.. note::
Those people made SleekXMPP, so you should not bother them if
you have an issue with slixmpp. But its still fair to credit
them for their work.
**Main Author:** `Nathan Fritz <http://andyet.net/team/fritzy>`_ **Main Author:** `Nathan Fritz <http://andyet.net/team/fritzy>`_
`fritzy@netflint.net <xmpp:fritzy@netflint.net?message>`_, `fritzy@netflint.net <xmpp:fritzy@netflint.net?message>`_,

125
docs/using_asyncio.rst Normal file
View File

@ -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 arent 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 <message>.
.. 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()

View File

@ -11,20 +11,12 @@
See the file LICENSE for copying permission. 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 logging
import unittest
import distutils.core
import datetime
from glob import glob from os.path import basename, join as pjoin
from os.path import splitext, basename, join as pjoin
from argparse import ArgumentParser from argparse import ArgumentParser
from urllib import urlopen from urllib import urlopen
from getpass import getpass
import slixmpp import slixmpp
from slixmpp.plugins.xep_0323.device import Device from slixmpp.plugins.xep_0323.device import Device
@ -186,5 +178,5 @@ if __name__ == '__main__':
logging.debug("ready ending") logging.debug("ready ending")
else: else:
print "noopp didn't happen" print("noopp didn't happen")

View File

@ -15,6 +15,7 @@ from argparse import ArgumentParser
import slixmpp import slixmpp
from slixmpp.exceptions import IqError, IqTimeout from slixmpp.exceptions import IqError, IqTimeout
from slixmpp.xmlstream.asyncio import asyncio
class Disco(slixmpp.ClientXMPP): class Disco(slixmpp.ClientXMPP):
@ -53,6 +54,7 @@ class Disco(slixmpp.ClientXMPP):
# our roster. # our roster.
self.add_event_handler("session_start", self.start) self.add_event_handler("session_start", self.start)
@asyncio.coroutine
def start(self, event): def start(self, event):
""" """
Process the session_start event. Process the session_start event.
@ -79,17 +81,17 @@ class Disco(slixmpp.ClientXMPP):
# received. Non-blocking options would be to listen # received. Non-blocking options would be to listen
# for the disco_info event, or passing a handler # for the disco_info event, or passing a handler
# function using the callback parameter. # function using the callback parameter.
info = self['xep_0030'].get_info(jid=self.target_jid, info = yield from self['xep_0030'].get_info(jid=self.target_jid,
node=self.target_node, node=self.target_node,
block=True) coroutine=True)
elif self.get in self.items_types: if self.get in self.items_types:
# The same applies from above. Listen for the # The same applies from above. Listen for the
# disco_items event or pass a callback function # disco_items event or pass a callback function
# if you need to process a non-blocking request. # if you need to process a non-blocking request.
items = self['xep_0030'].get_items(jid=self.target_jid, items = yield from self['xep_0030'].get_items(jid=self.target_jid,
node=self.target_node, node=self.target_node,
block=True) coroutine=True)
else: if self.get not in self.info_types and self.get not in self.items_types:
logging.error("Invalid disco request type.") logging.error("Invalid disco request type.")
return return
except IqError as e: except IqError as e:
@ -143,7 +145,7 @@ if __name__ == '__main__':
parser.add_argument("-p", "--password", dest="password", parser.add_argument("-p", "--password", dest="password",
help="password to use") help="password to use")
parser.add_argument("query", choices=["all", "info", "items", "identities", "features"]) 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='?') parser.add_argument("node", nargs='?')
args = parser.parse_args() args = parser.parse_args()

View File

@ -11,11 +11,11 @@
import logging import logging
from getpass import getpass from getpass import getpass
import threading
from argparse import ArgumentParser from argparse import ArgumentParser
import slixmpp import slixmpp
from slixmpp.exceptions import XMPPError from slixmpp.exceptions import XMPPError
from slixmpp import asyncio
FILE_TYPES = { FILE_TYPES = {
@ -40,8 +40,14 @@ class AvatarDownloader(slixmpp.ClientXMPP):
self.add_event_handler('avatar_metadata_publish', self.on_avatar) self.add_event_handler('avatar_metadata_publish', self.on_avatar)
self.received = set() 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): def start(self, event):
""" """
Process the session_start event. Process the session_start event.
@ -56,16 +62,20 @@ class AvatarDownloader(slixmpp.ClientXMPP):
data. data.
""" """
self.send_presence() self.send_presence()
self.get_roster() self.get_roster(callback=self.roster_received_cb)
print('Waiting for presence updates...\n') 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() self.disconnect()
@asyncio.coroutine
def on_vcard_avatar(self, pres): def on_vcard_avatar(self, pres):
print("Received vCard avatar update from %s" % pres['from'].bare) print("Received vCard avatar update from %s" % pres['from'].bare)
try: 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: except XMPPError:
print("Error retrieving avatar for %s" % pres['from']) print("Error retrieving avatar for %s" % pres['from'])
return return
@ -76,16 +86,18 @@ class AvatarDownloader(slixmpp.ClientXMPP):
pres['from'].bare, pres['from'].bare,
pres['vcard_temp_update']['photo'], pres['vcard_temp_update']['photo'],
filetype) filetype)
with open(filename, 'w+') as img: with open(filename, 'wb+') as img:
img.write(avatar['BINVAL']) img.write(avatar['BINVAL'])
@asyncio.coroutine
def on_avatar(self, msg): def on_avatar(self, msg):
print("Received avatar update from %s" % msg['from']) print("Received avatar update from %s" % msg['from'])
metadata = msg['pubsub_event']['items']['item']['avatar_metadata'] metadata = msg['pubsub_event']['items']['item']['avatar_metadata']
for info in metadata['items']: for info in metadata['items']:
if not info['url']: if not info['url']:
try: 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: except XMPPError:
print("Error retrieving avatar for %s" % msg['from']) print("Error retrieving avatar for %s" % msg['from'])
return return
@ -94,7 +106,7 @@ class AvatarDownloader(slixmpp.ClientXMPP):
filetype = FILE_TYPES.get(metadata['type'], 'png') filetype = FILE_TYPES.get(metadata['type'], 'png')
filename = 'avatar_%s_%s.%s' % (msg['from'].bare, info['id'], filetype) 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']) img.write(avatar['value'])
else: else:
# We could retrieve the avatar via HTTP, etc here instead. # 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. Wait to receive updates from all roster contacts.
""" """
self.received.add(pres['from'].bare) self.received.add(pres['from'].bare)
print((len(self.received), len(self.client_roster.keys())))
if len(self.received) >= len(self.client_roster.keys()): if len(self.received) >= len(self.client_roster.keys()):
self.presences_received.set() self.presences_received.set()
else: else:

View File

@ -103,5 +103,5 @@ def on_session2(event):
new_xmpp.disconnect() new_xmpp.disconnect()
new_xmpp.add_event_handler('session_start', on_session2) new_xmpp.add_event_handler('session_start', on_session2)
if new_xmpp.connect(): new_xmpp.connect()
new_xmpp.process(block=True) new_xmpp.process()

View File

@ -12,6 +12,8 @@
import logging import logging
from getpass import getpass from getpass import getpass
from argparse import ArgumentParser from argparse import ArgumentParser
from slixmpp.exceptions import IqError, IqTimeout
from slixmpp import asyncio
import slixmpp import slixmpp
@ -36,6 +38,7 @@ class PingTest(slixmpp.ClientXMPP):
# our roster. # our roster.
self.add_event_handler("session_start", self.start) self.add_event_handler("session_start", self.start)
@asyncio.coroutine
def start(self, event): def start(self, event):
""" """
Process the session_start event. Process the session_start event.
@ -53,8 +56,8 @@ class PingTest(slixmpp.ClientXMPP):
self.get_roster() self.get_roster()
try: try:
rtt = self['xep_0199'].ping(self.pingjid, rtt = yield from self['xep_0199'].ping(self.pingjid,
timeout=10) timeout=10)
logging.info("Success! RTT: %s", rtt) logging.info("Success! RTT: %s", rtt)
except IqError as e: except IqError as e:
logging.info("Error pinging %s: %s", logging.info("Error pinging %s: %s",
@ -78,8 +81,7 @@ if __name__ == '__main__':
action="store_const", dest="loglevel", action="store_const", dest="loglevel",
const=logging.DEBUG, default=logging.INFO) const=logging.DEBUG, default=logging.INFO)
parser.add_argument("-t", "--pingto", help="set jid to ping", parser.add_argument("-t", "--pingto", help="set jid to ping",
action="store", type="string", dest="pingjid", dest="pingjid", default=None)
default=None)
# JID and password options. # JID and password options.
parser.add_argument("-j", "--jid", dest="jid", parser.add_argument("-j", "--jid", dest="jid",

View File

@ -7,7 +7,13 @@ from argparse import ArgumentParser
import slixmpp import slixmpp
from slixmpp.xmlstream import ET, tostring 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): class PubsubClient(slixmpp.ClientXMPP):
@ -41,8 +47,10 @@ class PubsubClient(slixmpp.ClientXMPP):
self.disconnect() self.disconnect()
def nodes(self): def nodes(self):
future, callback = make_callback()
try: 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']: for item in result['disco_items']['items']:
print(' - %s' % str(item)) print(' - %s' % str(item))
except: except:
@ -63,16 +71,20 @@ class PubsubClient(slixmpp.ClientXMPP):
def publish(self): def publish(self):
payload = ET.fromstring("<test xmlns='test'>%s</test>" % self.data) payload = ET.fromstring("<test xmlns='test'>%s</test>" % self.data)
future, callback = make_callback()
try: 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'] id = result['pubsub']['publish']['item']['id']
print('Published at item id: %s' % id) print('Published at item id: %s' % id)
except: except:
logging.error('Could not publish to: %s' % self.node) logging.error('Could not publish to: %s' % self.node)
def get(self): def get(self):
future, callback = make_callback()
try: 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']: for item in result['pubsub']['items']['substanzas']:
print('Retrieved item %s: %s' % (item['id'], tostring(item['payload']))) print('Retrieved item %s: %s' % (item['id'], tostring(item['payload'])))
except: except:
@ -80,28 +92,28 @@ class PubsubClient(slixmpp.ClientXMPP):
def retract(self): def retract(self):
try: 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)) print('Retracted item %s from node %s' % (self.data, self.node))
except: except:
logging.error('Could not retract item %s from node %s' % (self.data, self.node)) logging.error('Could not retract item %s from node %s' % (self.data, self.node))
def purge(self): def purge(self):
try: 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) print('Purged all items from node %s' % self.node)
except: except:
logging.error('Could not purge items from node %s' % self.node) logging.error('Could not purge items from node %s' % self.node)
def subscribe(self): def subscribe(self):
try: 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)) print('Subscribed %s to node %s' % (self.boundjid.bare, self.node))
except: except:
logging.error('Could not subscribe %s to node %s' % (self.boundjid.bare, self.node)) logging.error('Could not subscribe %s to node %s' % (self.boundjid.bare, self.node))
def unsubscribe(self): def unsubscribe(self):
try: 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)) print('Unsubscribed %s from node %s' % (self.boundjid.bare, self.node))
except: except:
logging.error('Could not unsubscribe %s from node %s' % (self.boundjid.bare, self.node)) logging.error('Could not unsubscribe %s from node %s' % (self.boundjid.bare, self.node))

View File

@ -90,7 +90,7 @@ class RegisterBot(slixmpp.ClientXMPP):
resp['register']['password'] = self.password resp['register']['password'] = self.password
try: try:
resp.send() yield from resp.send_coroutine()
logging.info("Account created for %s!" % self.boundjid) logging.info("Account created for %s!" % self.boundjid)
except IqError as e: except IqError as e:
logging.error("Could not register account: %s" % logging.error("Could not register account: %s" %

View File

@ -11,11 +11,11 @@
import logging import logging
from getpass import getpass from getpass import getpass
import threading
from argparse import ArgumentParser from argparse import ArgumentParser
import slixmpp import slixmpp
from slixmpp.exceptions import IqError, IqTimeout from slixmpp.exceptions import IqError, IqTimeout
from slixmpp.xmlstream.asyncio import asyncio
class RosterBrowser(slixmpp.ClientXMPP): class RosterBrowser(slixmpp.ClientXMPP):
@ -36,8 +36,9 @@ class RosterBrowser(slixmpp.ClientXMPP):
self.add_event_handler("changed_status", self.wait_for_presences) self.add_event_handler("changed_status", self.wait_for_presences)
self.received = set() self.received = set()
self.presences_received = threading.Event() self.presences_received = asyncio.Event()
@asyncio.coroutine
def start(self, event): def start(self, event):
""" """
Process the session_start event. Process the session_start event.
@ -51,8 +52,12 @@ class RosterBrowser(slixmpp.ClientXMPP):
event does not provide any additional event does not provide any additional
data. data.
""" """
future = asyncio.Future()
def callback(result):
future.set_result(None)
try: try:
self.get_roster() self.get_roster(callback=callback)
yield from future
except IqError as err: except IqError as err:
print('Error: %' % err.iq['error']['condition']) print('Error: %' % err.iq['error']['condition'])
except IqTimeout: except IqTimeout:
@ -61,7 +66,7 @@ class RosterBrowser(slixmpp.ClientXMPP):
print('Waiting for presence updates...\n') print('Waiting for presence updates...\n')
self.presences_received.wait(5) yield from asyncio.sleep(10)
print('Roster for %s' % self.boundjid.bare) print('Roster for %s' % self.boundjid.bare)
groups = self.client_roster.groups() groups = self.client_roster.groups()

View File

@ -20,7 +20,7 @@ class Boomerang(Endpoint):
@remote @remote
def throw(self): def throw(self):
print "Duck!" print("Duck!")

View File

@ -18,7 +18,7 @@ from argparse import ArgumentParser
import slixmpp import slixmpp
from slixmpp.exceptions import XMPPError from slixmpp.exceptions import XMPPError
from slixmpp import asyncio
class AvatarSetter(slixmpp.ClientXMPP): class AvatarSetter(slixmpp.ClientXMPP):
@ -33,6 +33,7 @@ class AvatarSetter(slixmpp.ClientXMPP):
self.filepath = filepath self.filepath = filepath
@asyncio.coroutine
def start(self, event): def start(self, event):
""" """
Process the session_start event. Process the session_start event.
@ -51,7 +52,7 @@ class AvatarSetter(slixmpp.ClientXMPP):
avatar_file = None avatar_file = None
try: try:
avatar_file = open(os.path.expanduser(self.filepath)) avatar_file = open(os.path.expanduser(self.filepath), 'rb')
except IOError: except IOError:
print('Could not find file: %s' % self.filepath) print('Could not find file: %s' % self.filepath)
return self.disconnect() return self.disconnect()
@ -65,32 +66,31 @@ class AvatarSetter(slixmpp.ClientXMPP):
avatar_file.close() avatar_file.close()
used_xep84 = False used_xep84 = False
try:
print('Publish XEP-0084 avatar data')
self['xep_0084'].publish_avatar(avatar)
used_xep84 = True
except XMPPError:
print('Could not publish XEP-0084 avatar')
try: print('Publish XEP-0084 avatar data')
print('Update vCard with avatar') result = yield from self['xep_0084'].publish_avatar(avatar, coroutine=True)
self['xep_0153'].set_avatar(avatar=avatar, mtype=avatar_type) if isinstance(result, XMPPError):
except XMPPError: print('Could not publish XEP-0084 avatar')
else:
used_xep84 = True
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') print('Could not set vCard avatar')
if used_xep84: if used_xep84:
try: print('Advertise XEP-0084 avatar metadata')
print('Advertise XEP-0084 avatar metadata') result = yield from self['xep_0084'].publish_avatar_metadata([
self['xep_0084'].publish_avatar_metadata([ {'id': avatar_id,
{'id': avatar_id, 'type': avatar_type,
'type': avatar_type, 'bytes': avatar_bytes}
'bytes': avatar_bytes} # We could advertise multiple avatars to provide
# We could advertise multiple avatars to provide # options in image type, source (HTTP vs pubsub),
# options in image type, source (HTTP vs pubsub), # size, etc.
# size, etc. # {'id': ....}
# {'id': ....} ], coroutine=True)
]) if isinstance(result, XMPPError):
except XMPPError:
print('Could not publish XEP-0084 metadata') print('Could not publish XEP-0084 metadata')
print('Wait for presence updates to propagate...') print('Wait for presence updates to propagate...')

View File

@ -32,6 +32,9 @@ class Iq(RootStanza):
as a carrier stanza for an application-specific protocol instead. as a carrier stanza for an application-specific protocol instead.
Example <iq> Stanzas: Example <iq> Stanzas:
.. code-block:: xml
<iq to="user@example.com" type="get" id="314"> <iq to="user@example.com" type="get" id="314">
<query xmlns="http://jabber.org/protocol/disco#items" /> <query xmlns="http://jabber.org/protocol/disco#items" />
</iq> </iq>
@ -47,20 +50,9 @@ class Iq(RootStanza):
</iq> </iq>
Stanza Interface: Stanza Interface:
query -- The namespace of the <query> element if one exists. - **query**: The namespace of the <query> element if one exists.
Attributes: Attributes:
types -- May be one of: get, set, result, or error. - **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 <query> element.
get_query -- Return the namespace of the <query> element.
del_query -- Remove the <query> element.
reply -- Overrides StanzaBase.reply
send -- Overrides StanzaBase.send
""" """
namespace = 'jabber:client' namespace = 'jabber:client'
@ -98,8 +90,9 @@ class Iq(RootStanza):
""" """
Set the XML contents of the <iq> stanza. Set the XML contents of the <iq> stanza.
Arguments: :param value: An XML object or a list of XML objects to use as the <iq>
value -- An XML object to use as the <iq> stanza's contents stanza's contents
:type value: list or XML object
""" """
self.clear() self.clear()
StanzaBase.set_payload(self, value) StanzaBase.set_payload(self, value)
@ -111,8 +104,7 @@ class Iq(RootStanza):
Query elements are differentiated by their namespace. Query elements are differentiated by their namespace.
Arguments: :param str value: The namespace of the <query> element.
value -- The namespace of the <query> element.
""" """
query = self.xml.find("{%s}query" % value) query = self.xml.find("{%s}query" % value)
if query is None and value: if query is None and value:
@ -126,7 +118,9 @@ class Iq(RootStanza):
return self return self
def get_query(self): def get_query(self):
"""Return the namespace of the <query> element.""" """Return the namespace of the <query> element.
:rtype: str"""
for child in self.xml: for child in self.xml:
if child.tag.endswith('query'): if child.tag.endswith('query'):
ns = child.tag.split('}')[0] ns = child.tag.split('}')[0]
@ -144,16 +138,15 @@ class Iq(RootStanza):
def reply(self, clear=True): def reply(self, clear=True):
""" """
Send a reply <iq> stanza. Create a new <iq> stanza replying to ``self``.
Overrides StanzaBase.reply Overrides StanzaBase.reply
Sets the 'type' to 'result' in addition to the default Sets the 'type' to 'result' in addition to the default
StanzaBase.reply behavior. StanzaBase.reply behavior.
Arguments: :param bool clear: Indicates if existing content should be
clear -- Indicates if existing content should be removed before replying. Defaults to True.
removed before replying. Defaults to True.
""" """
new_iq = StanzaBase.reply(self, clear=clear) new_iq = StanzaBase.reply(self, clear=clear)
new_iq['type'] = 'result' new_iq['type'] = 'result'
@ -168,10 +161,8 @@ class Iq(RootStanza):
Overrides StanzaBase.send Overrides StanzaBase.send
Arguments: :param int timeout: The length of time (in seconds) to wait for a
response before an IqTimeout is raised
timeout -- The length of time (in seconds) to wait for a
response before an IqTimeout is raised
""" """
future = asyncio.Future() future = asyncio.Future()
@ -216,20 +207,19 @@ class Iq(RootStanza):
Overrides StanzaBase.send Overrides StanzaBase.send
Arguments: :param function callback: Optional reference to a stream handler
function. Will be executed when a reply
callback -- Optional reference to a stream handler stanza is received.
function. Will be executed when a reply stanza is :param int timeout: The length of time (in seconds) to wait for a
received. response before the timeout_callback is called,
timeout -- The length of time (in seconds) to wait for a instead of the regular callback
response before the timeout_callback is called, :param function timeout_callback: Optional reference to a stream handler
instead of the regular callback function. Will be executed when the
timeout_callback -- Optional reference to a stream handler timeout expires before a response has
function. Will be executed when the timeout expires been received for the originally-sent
before a response has been received with the IQ stanza.
originally-sent IQ stanza. :param bool coroutine: This function will return a coroutine if this
coroutine -- This function will return a coroutine if this argument argument is True.
is True.
""" """
if self.stream.session_bind_event.is_set(): if self.stream.session_bind_event.is_set():
matcher = MatchIDSender({ matcher = MatchIDSender({

View File

@ -23,6 +23,9 @@ class Message(RootStanza):
an error response. an error response.
Example <message> stanzas: Example <message> stanzas:
.. code-block:: xml
<message to="user1@example.com" from="user2@example.com"> <message to="user1@example.com" from="user2@example.com">
<body>Hi!</body> <body>Hi!</body>
</message> </message>
@ -32,26 +35,13 @@ class Message(RootStanza):
</message> </message>
Stanza Interface: Stanza Interface:
body -- The main contents of the message. - **body**: The main contents of the message.
subject -- An optional description of the message's contents. - **subject**: An optional description of the message's contents.
mucroom -- (Read-only) The name of the MUC room that sent the message. - **mucroom**: (Read-only) The name of the MUC room that sent the message.
mucnick -- (Read-only) The MUC nickname of message's sender. - **mucnick**: (Read-only) The MUC nickname of message's sender.
Attributes: Attributes:
types -- May be one of: normal, chat, headline, groupchat, or error. - **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.
""" """
name = 'message' name = 'message'
@ -81,18 +71,25 @@ class Message(RootStanza):
Overrides default stanza interface behavior. Overrides default stanza interface behavior.
Returns 'normal' if no type attribute is present. Returns 'normal' if no type attribute is present.
:rtype: str
""" """
return self._get_attr('type', 'normal') return self._get_attr('type', 'normal')
def get_parent_thread(self): 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) thread = self.xml.find('{%s}thread' % self.namespace)
if thread is not None: if thread is not None:
return thread.attrib.get('parent', '') return thread.attrib.get('parent', '')
return '' return ''
def set_parent_thread(self, value): 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) thread = self.xml.find('{%s}thread' % self.namespace)
if value: if value:
if thread is None: if thread is None:
@ -128,10 +125,11 @@ class Message(RootStanza):
Sets proper 'to' attribute if the message is from a MUC, and Sets proper 'to' attribute if the message is from a MUC, and
adds a message body if one is given. adds a message body if one is given.
Arguments: :param str body: Optional text content for the message.
body -- Optional text content for the message. :param bool clear: Indicates if existing content should be removed
clear -- Indicates if existing content should be removed before replying. Defaults to True.
before replying. Defaults to True.
:rtype: :class:`~.Message`
""" """
new_message = StanzaBase.reply(self, clear) new_message = StanzaBase.reply(self, clear)
@ -152,6 +150,8 @@ class Message(RootStanza):
Return the name of the MUC room where the message originated. Return the name of the MUC room where the message originated.
Read-only stanza interface. Read-only stanza interface.
:rtype: str
""" """
if self['type'] == 'groupchat': if self['type'] == 'groupchat':
return self['from'].bare return self['from'].bare
@ -163,6 +163,8 @@ class Message(RootStanza):
Return the nickname of the MUC user that sent the message. Return the nickname of the MUC user that sent the message.
Read-only stanza interface. Read-only stanza interface.
:rtype: str
""" """
if self['type'] == 'groupchat': if self['type'] == 'groupchat':
return self['from'].resource return self['from'].resource

View File

@ -27,6 +27,9 @@ class Presence(RootStanza):
to help keep the network running smoothly. to help keep the network running smoothly.
Example <presence> stanzas: Example <presence> stanzas:
.. code-block:: xml
<presence /> <presence />
<presence from="user@example.com"> <presence from="user@example.com">
@ -40,24 +43,14 @@ class Presence(RootStanza):
<presence to="user@otherhost.com" type="subscribe" /> <presence to="user@otherhost.com" type="subscribe" />
Stanza Interface: Stanza Interface:
priority -- A value used by servers to determine message routing. - **priority**: A value used by servers to determine message routing.
show -- The type of status, such as away or available for chat. - **show**: The type of status, such as away or available for chat.
status -- Custom, human readable status message. - **status**: Custom, human readable status message.
Attributes: Attributes:
types -- One of: available, unavailable, error, probe, - **types**: One of: available, unavailable, error, probe,
subscribe, subscribed, unsubscribe, subscribe, subscribed, unsubscribe, and unsubscribed.
and unsubscribed. - **showtypes**: One of: away, chat, dnd, and xa.
showtypes -- One of: away, chat, dnd, and xa.
Methods:
setup -- Overrides StanzaBase.setup
reply -- Overrides StanzaBase.reply
set_show -- Set the value of the <show> element.
get_type -- Get the value of the type attribute or <show> element.
set_type -- Set the value of the type attribute or <show> element.
get_priority -- Get the value of the <priority> element.
set_priority -- Set the value of the <priority> element.
""" """
name = 'presence' name = 'presence'
@ -93,8 +86,7 @@ class Presence(RootStanza):
""" """
Set the value of the <show> element. Set the value of the <show> element.
Arguments: :param str show: Must be one of: away, chat, dnd, or xa.
show -- Must be one of: away, chat, dnd, or xa.
""" """
if show is None: if show is None:
self._del_sub('show') self._del_sub('show')
@ -119,8 +111,7 @@ class Presence(RootStanza):
Set the type attribute's value, and the <show> element Set the type attribute's value, and the <show> element
if applicable. if applicable.
Arguments: :param str value: Must be in either self.types or self.showtypes.
value -- Must be in either self.types or self.showtypes.
""" """
if value in self.types: if value in self.types:
self['show'] = None self['show'] = None
@ -146,14 +137,15 @@ class Presence(RootStanza):
Bot clients should typically use a priority of 0 if the same Bot clients should typically use a priority of 0 if the same
JID is used elsewhere by a human-interacting client. JID is used elsewhere by a human-interacting client.
Arguments: :param int value: An integer value greater than or equal to 0.
value -- An integer value greater than or equal to 0.
""" """
self._set_sub_text('priority', text=str(value)) self._set_sub_text('priority', text=str(value))
def get_priority(self): def get_priority(self):
""" """
Return the value of the <presence> element as an integer. Return the value of the <presence> element as an integer.
:rtype: int
""" """
p = self._get_sub_text('priority') p = self._get_sub_text('priority')
if not p: if not p:
@ -166,13 +158,12 @@ class Presence(RootStanza):
def reply(self, clear=True): def reply(self, clear=True):
""" """
Set the appropriate presence reply type. Create a new reply <presence/> stanza from ``self``.
Overrides StanzaBase.reply. Overrides StanzaBase.reply.
Arguments: :param bool clear: Indicates if the stanza contents should be removed
clear -- Indicates if the stanza contents should be removed before replying. Defaults to True.
before replying. Defaults to True.
""" """
new_presence = StanzaBase.reply(self, clear) new_presence = StanzaBase.reply(self, clear)
if self['type'] == 'unsubscribe': if self['type'] == 'unsubscribe':

View File

@ -1474,7 +1474,7 @@ class StanzaBase(ElementBase):
Only type values contained in :attr:`types` are accepted. 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: if value in self.types:
self.xml.attrib['type'] = value self.xml.attrib['type'] = value
@ -1499,8 +1499,8 @@ class StanzaBase(ElementBase):
def set_from(self, value): def set_from(self, value):
"""Set the 'from' attribute of the stanza. """Set the 'from' attribute of the stanza.
Arguments: :param from: A string or JID object representing the sender's JID.
from -- A string or JID object representing the sender's JID. :type from: str or :class:`.JID`
""" """
return self._set_attr('from', str(value)) return self._set_attr('from', str(value))