Can now use len(self.client_roster) to get the number of JIDs in
the roster, and self.client_roster.groups() to get a dict of
groups and the JIDs in those groups.
Form fields now remember their current type if the type is deleted. This
allows for fields to properly format their values if set after the form
has been changed to the 'submit' type.
IqError is now caught and forwarded to the command error handler referenced
in the session.
Errors are now caught and processed by the session's error handler
whether or not the results Iq stanza includes the <command> substanza.
Added the option for blocking command calls. The blocking option is set
during start_command with block=True. Subsequent command flow methods use
session['block'] to determine their blocking behaviour.
If you use blocking commands, then you will need to wrap your command calls
in a try/except block for IqTimeout exceptions.
Changes:
May now use underscored method names
form.field is replaced by form['fields']
form.get_fields no longer accepts use_dict parameter, it always
returns an OrderedDict now
form.set_fields will accept either an OrderedDict, or a list
of (var, dict) tuples as before.
Setting the form type to 'submit' will remove extra meta data
from the form fields, leaving just the 'var' and 'value'
Setting the form type to 'cancel' will remove all fields.
Honestly, this is mainly just a demo/proof of concept that we
can handle dependencies and ordering issues with stream features.
DON'T use XEP-0078 if you are able to use the normal SASL method,
which should be the case unless you are dealing with a very old
XMPP server implementation.
Sleek loads a few plugins by default, which made it difficult to
configure or even disable them.
Now, if a plugin is registered without any configuration, then
sleek will try finding a configuration in self.plugin_config.
Thus, using the XEP-0082 and XEP-0202 introduces a dependency
on the dateutil package (installable using pip install python-dateutil).
Maybe we'll be able to rework how these plugins work to avoid
needing dateutil, but for now this will have to do.
Accepting download requests can be done using:
self['xep_0066'].register_url_handler(handler=self.oob_download)
# Add jid=... to specify a handler for a particular JID for a
# componenent.
def oob_download(self, iq):
if iq['from'] not in self.custom_oob_whitelist:
raise XMPPError('not-authorized')
try:
data = urllib2.urlopen(iq['oob_transfer']['url'])
file = open('oob_download', 'w+')
file.write(data.read())
file.close()
data.close()
except:
raise XMPPError('item-not-found')
If wait=True, then the disconnect call will block until
the send queue has emptied.
WARNING: Using wait=True when more stanzas are being added to the
queue than can be processed such that the queue is never empty
will cause the disconnect call to block indefinitely without actually
disconnecting.
The error bubbles through the event processing loop, breaking it and
hanging the application.
Instead, there is now a .exception(e) method on XMLStream which may
be overridden or reassigned that will receive all unhandled exceptions
(read: not XMPPError) from event and stream handlers.
ANONYMOUS was being treated as PLAIN, mechanism was being chosen
purely from supported mechanisms, not those provided by the server.
Broke nested handler methods into top-level methods.
If a stanza handler raised an exception, the exception was processed
and replied by the modified stanza, not a stanza with the original
content.
A copy is now made before handler processing, and if an exception occurs
it is the copy that processes the exception using the original content.
For now, session_end is the same as disconnected, but once support is
added later for stream management, the two events will become distinct.
Plugins should add handlers for session_end for cleaning any session
state.
Backoff was only being done for the initial connection attempt
before. Now any reconnection will start with a minimum 1 sec
delay which will approximately double between attempts.
JIDs with Unicode values were being encoded by the JID class
instead of leaving them as just Unicode strings.
It may still be a good idea to use
from __future__ import unicode_literals
pretty much everywhere though.
Fixes issue #88.
See issue #89
Using get_roster will now return the same types of values as
Iq.send. If a timeout occurs, then the event 'roster_timeout'
will be fired. A successful call to get_roster will also
raise the 'roster_received' event.
To ensure that the get_roster call was successful, here
is a pattern to follow:
def __init__(self, ...):
...
self.add_event_handler('session_start', self.session_start)
self.add_event_handler('roster_timeout', self.roster_timeout)
self.add_event_handler('roster_received', self.roster_received)
def session_start(self, e):
self.send_presence()
self.get_roster()
def roster_timeout(self, e):
# Optionally increase the timeout period
self.get_roster(timeout=self.response_timeout * 2)
def roster_received(self, iq):
# Do stuff, roster has been initialized.
...
Since camelcase names are aliased to the underscored name at startup,
if the underscored version is replaced later, the camelCase name does
not reflect the change.
Each interface, say foo, may be overridden in three ways:
set_foo
get_foo
del_foo
To declare an override in a plugin, add the class field
overrides as so:
overrides = ['set_foo', 'del_foo']
Each override must have a matching set_foo(), etc method
for implementing the new behaviour.
To enable the overrides for a particular parent stanza,
pass the option overrides=True to register_stanza_plugin.
register_stanza_plugin(Stanza, Plugin, overrides=True)
Example code:
class Test(ElementBase):
name = 'test'
namespace = 'testing'
interfaces = set(('foo', 'bar'))
sub_interfaces = set(('bar',))
class TestOverride(ElementBase):
name = 'test-override'
namespace = 'testing'
plugin_attrib = 'override'
interfaces = set(('foo',))
overrides = ['set_foo']
def setup(self, xml):
# Don't include an XML element in the parent stanza
# since we're adding just an attribute.
# If adding a regular subelement, no need to do this.
self.xml = ET.Element('')
def set_foo(self, value):
print("overrides!")
self.parent()._set_attr('foo', 'override-%s' % value)
register_stanza_plugin(Test, TestOverride, overrides=True)
Example usage:
>>> t = TestStanza()
>>> t['foo'] = 'bar'
>>> t['foo']
'override-bar'
Backward incompatibility alert!
Please see examples/adhoc_provider.py for how to use the new
plugin implementation, or the test examples in the files
tests/test_stream_xep_0050.py and tests/test_stanza_xep_0050.py.
Major changes:
- May now have zero-step commands. Useful if a command is
intended to be a dynamic status report that doesn't
require any user input.
- May use payloads other than data forms, such as a
completely custom stanza type.
- May include multiple payload items, such as multiple
data forms, or a form and a custom stanza type.
- Includes a command user API for calling adhoc commands
on remote agents and managing the workflow.
- Added support for note elements.
Todo:
- Add prev action support.
You may use register_plugin('old_0050') to continue using the
previous XEP-0050 implementation.
ElementBase instances will display the top-most namespace by default.
StanzaBase instances will NOT display the top-most namespace by default.
May pass True or False to __str__ to override.
We need to check if type="get". otherwise we will send our version
when we will receive the version of the remote entity, and thus
going in an infinite loop.
Can now be used as so:
>>> msg['chat_state']
''
>>> msg
<message />
>>> msg['chat_state'] = 'paused'
>>> msg
<message>
<paused xmlns="http://jabber.org/protocol/chatstates" />
</message>
>>> msg['chat_state']
'paused'
>>> del msg['chat_state']
>>> msg
<message />
Now done more responsibly, saving any existing signal handlers
and calling them when an interrupt occurs in addition to the
one Sleek installs.
NOTE: You may need to explicitly use "kill <process id>" in
order to trigger the proper signal handler execution, and
to raise the "killed" event.
Instead of the actual callback object, return just the name of
the callback object created when using iq.send(callback=..).
This will help prevent memory leaks by not keeping an additional
reference to the object, but still allows for the callback to be
canceled by using self.remove_handler("handler_name").
Waiting until the actual run step means that the handler is not
marked for deletion when checked in the __spawn_event() thread,
causing the callback to stay in the handler list.
This allows exceptions to include the original
content of a stanza in the error response by including
the parameter clear=False when raising the exception.
Stream features now use stanza objects!
Features are given a ranking that expresses the dependency
relationships (since only one feature is negotiated at a time, the
dependency graph can be replaced by a line).
>>> xmpp.register_feature('my_feature', _my_handler,
>>> restart=True, # Requires stream restart
>>> order=600) # Ranking (out of ~ 10,000,
>>> # lower #'s executed first)
SASL mechanisms may now be added or disabled as needed. Each mechanism
is given a priority value indicating the order in which the client
wishes for mechanisms to be tried. Higher priority numbers are executed
first.
>>> xmpp.register_sasl_mechanism('SASL-MECH', _mech_handler,
>>> priority=0)
Disabling a SASL mechanism:
>>> xmpp.remove_sasl_mechanism('ANONYMOUS')
Will now always show top-level namespace, unless it is the same
as the stream's default namespace. Also added the XMPP stream
namespace to the namespace map as 'stream'.
Use stanza.values instead of _get/set_stanza_values where used.
ElementBase stanzas can now use .tag
May use class method tag_name() for stanza classes.
ElementBase now has .clear() method.
May now use register_stanza_plugin(Foo, Bar, iterable=True)
to add to the set of stanza classes used for iterable
substanzas. It is no longer necessary to manually specify
the contents of subitem if the new method is used.
A stanza object may add is_extension = True to its class definition
to provide a single new interface to a parent stanza.
For example:
import sleekxmpp
from sleekxmpp import Iq
from sleekxmpp.xmlstream import ElementBase, register_stanza_plugin, ET
class Foo(ElementBase):
"""
Test adding just an attribute to a parent stanza.
Adding subelements works as expected.
"""
is_extension = True
interfaces = set(('foo',))
plugin_attrib = 'foo'
def setup(self, xml):
# Don't include an XML element in the parent stanza
# since we're adding just an attribute.
# If adding a regular subelement, no need to do this.
self.xml = ET.Element('')
def set_foo(self, val):
self.parent()._set_attr('foo', val)
def get_foo(self):
return self.parent()._get_attr('foo')
def del_foo(self):
self.parent()._del_attr('foo')
register_stanza_plugin(Iq, Foo)
i1 = Iq()
i2 = Iq(xml=ET.fromstring("<iq xmlns='jabber:client' foo='bar' />"))
>>> i1['foo'] = '3'
>>> i1
'3'
>>> i1
'<iq id="0" foo="3" />'
>>> i2
'<iq id="0" foo="bar" />'
>>> i2['foo']
'bar'
>>> del i2['foo']
>>> i2
'<iq id="0" />'