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" />'
We now raise the unexpected exceptions instead of sending
them on the network.
- avoids flood (sending a traceback on a MUC, for example…) and
maybe some security issues.
- lets you handle the traceback (catch it to handle
it properly, or with except_hook, etc)
- an exception cannot be raised without you knowing
Daemonized threads exit once the main program has quit,
and the only threads left running are all daemon threads.
Should fix hanging clients while not trampling over anyone
else's signal handlers.
Methods now accept either an ifrom or mfrom parameter
to specify a 'from' value. Client connections should not
need to use these, but component connections must use them.
May include a to and from JID in make_iq_* calls.
May pass an existing iq stanza to most of them instead of generating
a new stanza.
make_iq now accepts a 'to' value, 'type' value, and 'query' value to
simplify things a bit more.
Now with dynamic node handling goodness.
Some things are not quite working yet, in particular:
set_items
set_info
set_identities
set_features
And still need more unit tests to round things out.
Support is only for adding literal XML content
to stanzas. Full support for things like multiple
message bodies with different xml:lang values is
still in the works.
The callback will be a stream level handler, and will not
execute in its own thread. If you must have a thread, have the
callback function raise a custom event, which can be processed
by another event handler, which may run in an individual thread,
like so:
def handle_reply(self, iq):
self.event('custom_event', iq)
def do_long_operation_in_thread(self, iq):
...
self.add_event_handler('custom_event', self.do_long_operation_in_thread)
...take out already prepared iq stanza...
iq.send(callback=self.handle_reply)
SleekTest can now use matchers when checking stanzas, using
the method parameter for self.check(), self.recv(), and self.send():
method='exact' - Same behavior as before
'xpath' - Use xpath matcher
'id' - Use ID matcher
'mask' - Use XML mask matcher
'stanzapath' - Use StanzaPath matcher
recv_feature and send_feature only accept 'exact' and 'mask' for now.
Just catch an other exception type coming from the dns resolver that
could be raised with hosts like "anon.example.com" which just don't have
any SRV record.
* check_stanza does not require stanza_class parameter. Introspection!
* check_message, check_iq, and check_presence removed -- use check
instead.
* stream_send_stanza, stream_send_message, stream_send_iq, and
stream_send_presence removed -- use send instead.
* Use recv instead of recv_message, recv_presence, etc.
* check_jid instead of check_JID
* stream_start may accept multi=True to return a new SleekTest instance
for testing multiple streams at once.
xep_0030 still referenced event_handlers. Added the method event_handled
which will return the number of registered handlers for an event to
resolve the issue.
Setting signal handlers from inside a thread is not supported in Python,
but some applications need to run Sleek from a child thread.
SleekXMPP applications that run inside a child thread will NOT be able
to detect SIGHUP or SIGTERM events. Those must be caught and managed by
the main program.
Made setting the SIG* handlers conditional on if the signal defined for
the OS.
Added the attribute ssl_version to XMLStream to set the version of SSL
used during connection. It defaults to ssl.PROTOCOL_TLSv1, but OpenFire
tends to require ssl.PROTOCOL_SSLv23.
Raising an XMPPError exception from an event handler now works, even if
from a threaded handler.
Added stream tests to verify.
We should start using XMPPError, it really makes things simple!
Stanza objects now accept the use of underscored names.
The CamelCase versions are still available for backwards compatibility,
but are discouraged.
The property stanza.values now maps to the old getStanzaValues and
setStanzaValues, in addition to _set_stanza_values and
_get_stanza_values.
It is now possible to ask for "any number of history stanzas" when
joining a muc (with history=None).
Also we use "maxchars" when asking NO history ("0") since it's a MUST in
the XEP.
And you can specify a message when leaving a MUC.
Implemented ANONYMOUS authentication on the ClientXMPP class.
To use it, you just need to provide a domain (e.g 'anon.example.com')
with an optional resource (e.g 'anon.example.com/resource') as the JID,
with no password. The JID class has been improved to accept
domains as fulljid.
You can test this with echo_client.py
python echo_client.py -j anon.louiz.org/ # anonymous with a resource
# defined by the server
python echo_client.py -j anon.louiz.org/resource # anonymous with given
# resource
The "normal" authentication method still works exactly like before.
Moved SleekTest to sleekxmpp.test package.
Corrected error in XML compare method.
Added TestLiveSocket to run stream tests against live streams.
Modified XMLStream to work with TestLiveSocket.
Moved SleekTest to sleekxmpp.test.
Organized test suites by their focus.
- Suites focused on testing stanza objects are named test_stanza_X.py
- Suites focused on testing stream behavior are name test_stream_X.py