If calling disconnect() from a non-threaded event handler, deadlock can
happen as disconnect() is waiting for threads to close, but the event
runner is blocked by a handler waiting for disconnect() to return.
It is best to specify threaded=True for event handlers which may call
disconnect().
- Add option for disconnecting without sending </stream>:
self.disconnect(send_close=False)
- Optionally distinguish between session_end and disconnected based
on if </stream> was sent.
self.end_session_on_disconnect = False
The set of bool_interfaces provides default behaviour for
checking/setting the presence of empty subelements.
The prime example of this would be:
bool_interfaces = set(['required'])
This would mean that ``stanza['required']`` would return ``True`` for:
<stanza>
<required />
</stanza>
and ``False`` for:
<stanza />
Likewise, assigning ``stanza['required'] = True`` would add an empty
``<required />`` element, and setting it to ``False`` would remove
such an element if it exists.
Added option to XMLStream.send() to skip applying filters.
Filters in the out_sync group are synced with placing stanza content
either on the wire directly or into the send queue. Because of this,
out_sync filters should not block.
Setting self.reconnect_max_attempts to a non-None value will limit
the number of times a connection attempt will be made before quiting
and raising a 'connection_failed' event.
The payload is a dictionary of parsed cert data, as provided by
Python's getpeercert() socket method. It unfortunately does not
provide much detail beyond basic info.
The parsing and namespace cleaning isn't terribly expensive, but it does
add up. It was adding an extra 5sec when processing 100,000 basic
message stanzas.
Based on profiling, using around 35 stream handlers quarters the number
of basic message stanzas that can be processed in a second, in
comparison to only using the bare minimum of four handlers.
To help, we can drop handlers for stream features once the session
has started. So that we can re-enable these handlers when a stream
must restart, the 'stream_start' event has been added which fires
whenever a stream header is received.
The 'stream_start' event is a more generic replacement for the
existing start_stream_handler() method.
This allows applications to filter out sensitive information, such
as passwords, so that it won't appear in the logs.
It does mean that the debug logs will not show the actual received
data, and there will be no indication of tampering, unless the
filter author explicitly logs and notes that a change was made.
A filter accepts and returns a stanza, but potentially modified.
To prevent sending/receiving a stanza, a filter may return None.
Incoming:
self.add_filter('in', in_filter)
Outgoing:
self.add_filter('out', out_filter)
Filters are applied in the order thay are added. However, you may
add an order parameter, which is the place in the list to insert the
filter:
self.add_filter('in', in_filter, order=0)
May set self.disconnect_wait=True so that all disconnect
calls wait for the send queue to empty, unless explicitly
overridden with wait=False.
The session_end now fires before closing the socket so
that final stanzas may be sent, such as unavailable presences
for components.
Calling reconnect() simultaneously from multiple threads (like when
using XEP-0199 keepalive) could break because the connection state
can transition and break the state expectations in one of the
reconnect() calls.
Note that using % in a string will _always_ perform the sting substitutions, because the strings are constructed before the function is called. So log.debug('%s' % expensiveoperation()) will take about the same CPU time whether or not the logging level is DEBUG or INFO. if you use , no substitutions are performed unless the string is actually logged
Note that using % in a string will _always_ perform the sting substitutions, because the strings are constructed before the function is called. So log.debug('%s' % expensiveoperation()) will take about the same CPU time whether or not the logging level is DEBUG or INFO. if you use , no substitutions are performed unless the string is actually logged
The processing loop was continuing to call __read_xml after </stream>
was received, which caused SyntaxErrors (can't find starting element).
This should fix issue #102
May be disabled by setting:
self.whitespace_keepalive = False
The keepalive interval can be adjusted using:
self.whitespace_keepalive_interval = 300
The default interval is 5min.
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.
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.
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'
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.
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.