Merge branch 'develop' into roster

Conflicts:
	sleekxmpp/basexmpp.py
This commit is contained in:
Lance Stout 2011-02-02 09:13:22 -05:00
commit de6170a13d
21 changed files with 291 additions and 196 deletions

View file

@ -33,7 +33,7 @@ class testps(sleekxmpp.ClientXMPP):
self.node = "pstestnode_%s" self.node = "pstestnode_%s"
self.pshost = pshost self.pshost = pshost
if pshost is None: if pshost is None:
self.pshost = self.server self.pshost = self.boundjid.host
self.nodenum = int(nodenum) self.nodenum = int(nodenum)
self.leafnode = self.nodenum + 1 self.leafnode = self.nodenum + 1
self.collectnode = self.nodenum + 2 self.collectnode = self.nodenum + 2

View file

@ -16,7 +16,7 @@ import sleekxmpp
from sleekxmpp import plugins from sleekxmpp import plugins
import sleekxmpp.roster as roster import sleekxmpp.roster as roster
from sleekxmpp.stanza import Message, Presence, Iq, Error from sleekxmpp.stanza import Message, Presence, Iq, Error, StreamError
from sleekxmpp.stanza.roster import Roster from sleekxmpp.stanza.roster import Roster
from sleekxmpp.stanza.nick import Nick from sleekxmpp.stanza.nick import Nick
from sleekxmpp.stanza.htmlim import HTMLIM from sleekxmpp.stanza.htmlim import HTMLIM
@ -133,6 +133,10 @@ class BaseXMPP(XMLStream):
Callback('Presence', Callback('Presence',
MatchXPath("{%s}presence" % self.default_ns), MatchXPath("{%s}presence" % self.default_ns),
self._handle_presence)) self._handle_presence))
self.register_handler(
Callback('Stream Error',
MatchXPath("{%s}error" % self.stream_ns),
self._handle_stream_error))
self.add_event_handler('disconnected', self.add_event_handler('disconnected',
self._handle_disconnected) self._handle_disconnected)
@ -165,6 +169,7 @@ class BaseXMPP(XMLStream):
self.register_stanza(Message) self.register_stanza(Message)
self.register_stanza(Iq) self.register_stanza(Iq)
self.register_stanza(Presence) self.register_stanza(Presence)
self.register_stanza(StreamError)
# Initialize a few default stanza plugins. # Initialize a few default stanza plugins.
register_stanza_plugin(Iq, Roster) register_stanza_plugin(Iq, Roster)
@ -606,6 +611,9 @@ class BaseXMPP(XMLStream):
"""When disconnected, reset the roster""" """When disconnected, reset the roster"""
self.roster = {} self.roster = {}
def _handle_stream_error(self, error):
self.event('stream_error', error)
def _handle_message(self, msg): def _handle_message(self, msg):
"""Process incoming message stanzas.""" """Process incoming message stanzas."""
self.event('message', msg) self.event('message', msg)

View file

@ -163,11 +163,13 @@ class ClientXMPP(BaseXMPP):
log.debug("Since no address is supplied," + \ log.debug("Since no address is supplied," + \
"attempting SRV lookup.") "attempting SRV lookup.")
try: try:
xmpp_srv = "_xmpp-client._tcp.%s" % self.server xmpp_srv = "_xmpp-client._tcp.%s" % self.boundjid.host
answers = dns.resolver.query(xmpp_srv, dns.rdatatype.SRV) answers = dns.resolver.query(xmpp_srv, dns.rdatatype.SRV)
except (dns.resolver.NXDOMAIN, dns.resolver.NoAnswer): except (dns.resolver.NXDOMAIN, dns.resolver.NoAnswer):
log.debug("No appropriate SRV record found." + \ log.debug("No appropriate SRV record found." + \
" Using JID server name.") " Using JID server name.")
except (dns.exception.Timeout,):
log.debug("DNS resolution timed out.")
else: else:
# Pick a random server, weighted by priority. # Pick a random server, weighted by priority.

View file

@ -177,7 +177,10 @@ class xep_0030(base_plugin):
elif node is None: elif node is None:
self._handlers[htype]['jid'][jid] = handler self._handlers[htype]['jid'][jid] = handler
elif jid is None: elif jid is None:
if self.xmpp.is_component:
jid = self.xmpp.boundjid.full jid = self.xmpp.boundjid.full
else:
jid = self.xmpp.boundjid.bare
self._handlers[htype]['node'][(jid, node)] = handler self._handlers[htype]['node'][(jid, node)] = handler
else: else:
self._handlers[htype]['node'][(jid, node)] = handler self._handlers[htype]['node'][(jid, node)] = handler
@ -342,7 +345,7 @@ class xep_0030(base_plugin):
""" """
self._run_node_handler('del_items', jid, node, kwargs) self._run_node_handler('del_items', jid, node, kwargs)
def add_item(self, jid=None, name='', node=None, subnode='', ijid=None): def add_item(self, jid='', name='', node=None, subnode='', ijid=None):
""" """
Add a new item element to the given JID/node combination. Add a new item element to the given JID/node combination.
@ -356,10 +359,12 @@ class xep_0030(base_plugin):
subnode -- Optional node for the item. subnode -- Optional node for the item.
ijid -- The JID to modify. ijid -- The JID to modify.
""" """
if not jid:
jid = self.xmpp.boundjid.full
kwargs = {'ijid': jid, kwargs = {'ijid': jid,
'name': name, 'name': name,
'inode': subnode} 'inode': subnode}
self._run_node_handler('add_item', jid, node, kwargs) self._run_node_handler('add_item', ijid, node, kwargs)
def del_item(self, jid=None, node=None, **kwargs): def del_item(self, jid=None, node=None, **kwargs):
""" """
@ -500,7 +505,10 @@ class xep_0030(base_plugin):
data -- Optional, custom data to pass to the handler. data -- Optional, custom data to pass to the handler.
""" """
if jid is None: if jid is None:
if self.xmpp.is_component:
jid = self.xmpp.boundjid.full jid = self.xmpp.boundjid.full
else:
jid = self.xmpp.boundjid.bare
if node is None: if node is None:
node = '' node = ''
@ -526,8 +534,12 @@ class xep_0030(base_plugin):
if iq['type'] == 'get': if iq['type'] == 'get':
log.debug("Received disco info query from " + \ log.debug("Received disco info query from " + \
"<%s> to <%s>." % (iq['from'], iq['to'])) "<%s> to <%s>." % (iq['from'], iq['to']))
if self.xmpp.is_component:
jid = iq['to'].full
else:
jid = iq['to'].bare
info = self._run_node_handler('get_info', info = self._run_node_handler('get_info',
iq['to'].full, jid,
iq['disco_info']['node'], iq['disco_info']['node'],
iq) iq)
iq.reply() iq.reply()
@ -552,8 +564,12 @@ class xep_0030(base_plugin):
if iq['type'] == 'get': if iq['type'] == 'get':
log.debug("Received disco items query from " + \ log.debug("Received disco items query from " + \
"<%s> to <%s>." % (iq['from'], iq['to'])) "<%s> to <%s>." % (iq['from'], iq['to']))
if self.xmpp.is_component:
jid = iq['to'].full
else:
jid = iq['to'].bare
items = self._run_node_handler('get_items', items = self._run_node_handler('get_items',
iq['to'].full, jid,
iq['disco_items']['node']) iq['disco_items']['node'])
iq.reply() iq.reply()
if items: if items:
@ -590,3 +606,4 @@ class xep_0030(base_plugin):
"Using default disco#info feature.") "Using default disco#info feature.")
info.add_feature(info.namespace) info.add_feature(info.namespace)
return info return info

View file

@ -247,8 +247,8 @@ class StaticDisco(object):
self.add_node(jid, node) self.add_node(jid, node)
self.nodes[(jid, node)]['items'].add_item( self.nodes[(jid, node)]['items'].add_item(
data.get('ijid', ''), data.get('ijid', ''),
node=data.get('inode', None), node=data.get('inode', ''),
name=data.get('name', None)) name=data.get('name', ''))
def del_item(self, jid, node, data): def del_item(self, jid, node, data):
""" """
@ -262,3 +262,4 @@ class StaticDisco(object):
self.nodes[(jid, node)]['items'].del_item( self.nodes[(jid, node)]['items'].del_item(
data.get('ijid', ''), data.get('ijid', ''),
node=data.get('inode', None)) node=data.get('inode', None))

View file

@ -316,6 +316,7 @@ class xep_0045(base.base_plugin):
x = ET.Element('{jabber:x:data}x', type='cancel') x = ET.Element('{jabber:x:data}x', type='cancel')
query.append(x) query.append(x)
iq = self.xmpp.makeIqSet(query) iq = self.xmpp.makeIqSet(query)
iq['to'] = room
iq.send() iq.send()
def setRoomConfig(self, room, config, ifrom=''): def setRoomConfig(self, room, config, ifrom=''):

View file

@ -36,7 +36,7 @@ class xep_0078(base.base_plugin):
log.debug("Starting jabber:iq:auth Authentication") log.debug("Starting jabber:iq:auth Authentication")
auth_request = self.xmpp.makeIqGet() auth_request = self.xmpp.makeIqGet()
auth_request_query = ET.Element('{jabber:iq:auth}query') auth_request_query = ET.Element('{jabber:iq:auth}query')
auth_request.attrib['to'] = self.xmpp.server auth_request.attrib['to'] = self.xmpp.boundjid.host
username = ET.Element('username') username = ET.Element('username')
username.text = self.xmpp.username username.text = self.xmpp.username
auth_request_query.append(username) auth_request_query.append(username)

View file

@ -84,5 +84,5 @@ class xep_0092(base_plugin):
result = iq.send() result = iq.send()
if result and result['type'] != 'error': if result and result['type'] != 'error':
return result['software_version']._get_stanza_values() return result['software_version'].values
return False return False

View file

@ -33,7 +33,7 @@ class xep_0199(base.base_plugin):
def scheduled_ping(self): def scheduled_ping(self):
log.debug("pinging...") log.debug("pinging...")
if self.sendPing(self.xmpp.server, self.config.get('timeout', 30)) is False: if self.sendPing(self.xmpp.boundjid.host, self.config.get('timeout', 30)) is False:
log.debug("Did not recieve ping back in time. Requesting Reconnect.") log.debug("Did not recieve ping back in time. Requesting Reconnect.")
self.xmpp.reconnect() self.xmpp.reconnect()

View file

@ -27,10 +27,12 @@ class EntityTime(ElementBase):
interfaces = set(('tzo', 'utc')) interfaces = set(('tzo', 'utc'))
sub_interfaces = set(('tzo', 'utc')) sub_interfaces = set(('tzo', 'utc'))
#def get_utc(self): # TODO: return a datetime.tzinfo object? #def get_tzo(self):
# TODO: Right now it returns a string but maybe it should
# return a datetime.tzinfo object or maybe a datetime.timedelta?
#pass #pass
def set_tzo(self, tzo): # TODO: support datetime.tzinfo objects? def set_tzo(self, tzo):
if isinstance(tzo, tzinfo): if isinstance(tzo, tzinfo):
td = datetime.now(tzo).utcoffset() # What if we are faking the time? datetime.now() shouldn't be used here' td = datetime.now(tzo).utcoffset() # What if we are faking the time? datetime.now() shouldn't be used here'
seconds = td.seconds + td.days * 24 * 3600 seconds = td.seconds + td.days * 24 * 3600
@ -45,7 +47,7 @@ class EntityTime(ElementBase):
# Returns a datetime object instead the string. Is this a good idea? # Returns a datetime object instead the string. Is this a good idea?
value = self._get_sub_text('utc') value = self._get_sub_text('utc')
if '.' in value: if '.' in value:
return datetime.strptime(value, '%Y-%m-%d.%fT%H:%M:%SZ') return datetime.strptime(value, '%Y-%m-%dT%H:%M:%S.%fZ')
else: else:
return datetime.strptime(value, '%Y-%m-%dT%H:%M:%SZ') return datetime.strptime(value, '%Y-%m-%dT%H:%M:%SZ')

View file

@ -8,6 +8,7 @@
from sleekxmpp.stanza.error import Error from sleekxmpp.stanza.error import Error
from sleekxmpp.stanza.stream_error import StreamError
from sleekxmpp.stanza.iq import Iq from sleekxmpp.stanza.iq import Iq
from sleekxmpp.stanza.message import Message from sleekxmpp.stanza.message import Message
from sleekxmpp.stanza.presence import Presence from sleekxmpp.stanza.presence import Presence

View file

@ -224,4 +224,3 @@ class Iq(RootStanza):
else: else:
StanzaBase._set_stanza_values(self, values) StanzaBase._set_stanza_values(self, values)
return self return self

View file

@ -44,7 +44,7 @@ class Nick(ElementBase):
del_nick -- Remove the <nick> element. del_nick -- Remove the <nick> element.
""" """
namespace = 'http://jabber.org/nick/nick' namespace = 'http://jabber.org/protocol/nick'
name = 'nick' name = 'nick'
plugin_attrib = name plugin_attrib = name
interfaces = set(('nick',)) interfaces = set(('nick',))

View file

@ -0,0 +1,69 @@
"""
SleekXMPP: The Sleek XMPP Library
Copyright (C) 2010 Nathanael C. Fritz
This file is part of SleekXMPP.
See the file LICENSE for copying permission.
"""
from sleekxmpp.stanza.error import Error
from sleekxmpp.xmlstream import StanzaBase, ElementBase, ET
from sleekxmpp.xmlstream import register_stanza_plugin
class StreamError(Error, StanzaBase):
"""
XMPP stanzas of type 'error' should include an <error> stanza that
describes the nature of the error and how it should be handled.
Use the 'XEP-0086: Error Condition Mappings' plugin to include error
codes used in older XMPP versions.
The stream:error stanza is used to provide more information for
error that occur with the underlying XML stream itself, and not
a particular stanza.
Note: The StreamError stanza is mostly the same as the normal
Error stanza, but with different namespaces and
condition names.
Example error stanza:
<stream:error>
<not-well-formed xmlns="urn:ietf:params:xml:ns:xmpp-streams" />
<text xmlns="urn:ietf:params:xml:ns:xmpp-streams">
XML was not well-formed.
</text>
</stream:error>
Stanza Interface:
condition -- The name of the condition element.
text -- Human readable description of the error.
Attributes:
conditions -- The set of allowable error condition elements.
condition_ns -- The namespace for the condition element.
Methods:
setup -- Overrides ElementBase.setup.
get_condition -- Retrieve the name of the condition element.
set_condition -- Add a condition element.
del_condition -- Remove the condition element.
get_text -- Retrieve the contents of the <text> element.
set_text -- Set the contents of the <text> element.
del_text -- Remove the <text> element.
"""
namespace = 'http://etherx.jabber.org/streams'
interfaces = set(('condition', 'text'))
conditions = set((
'bad-format', 'bad-namespace-prefix', 'conflict',
'connection-timeout', 'host-gone', 'host-unknown',
'improper-addressing', 'internal-server-error', 'invalid-from',
'invalid-namespace', 'invalid-xml', 'not-authorized',
'not-well-formed', 'policy-violation', 'remote-connection-failed',
'reset', 'resource-constraint', 'restricted-xml', 'see-other-host',
'system-shutdown', 'undefined-condition', 'unsupported-encoding',
'unsupported-feature', 'unsupported-stanza-type',
'unsupported-version'))
condition_ns = 'urn:ietf:params:xml:ns:xmpp-streams'

View file

@ -183,8 +183,7 @@ class SleekTest(unittest.TestCase):
""" """
Create and compare several stanza objects to a correct XML string. Create and compare several stanza objects to a correct XML string.
If use_values is False, test using getStanzaValues() and If use_values is False, tests using stanza.values will not be used.
setStanzaValues() will not be used.
Some stanzas provide default values for some interfaces, but Some stanzas provide default values for some interfaces, but
these defaults can be problematic for testing since they can easily these defaults can be problematic for testing since they can easily
@ -207,9 +206,8 @@ class SleekTest(unittest.TestCase):
values. These interfaces will be set to their values. These interfaces will be set to their
defaults for the given and generated stanzas to defaults for the given and generated stanzas to
prevent unexpected test failures. prevent unexpected test failures.
use_values -- Indicates if testing using getStanzaValues() and use_values -- Indicates if testing using stanza.values should
setStanzaValues() should be used. Defaults to be used. Defaults to True.
True.
""" """
if method is None and hasattr(self, 'match_method'): if method is None and hasattr(self, 'match_method'):
method = getattr(self, 'match_method') method = getattr(self, 'match_method')
@ -242,10 +240,10 @@ class SleekTest(unittest.TestCase):
stanza2 = stanza_class(xml=xml) stanza2 = stanza_class(xml=xml)
if use_values: if use_values:
# Using getStanzaValues() and setStanzaValues() will add # Using stanza.values will add XML for any interface that
# XML for any interface that has a default value. We need # has a default value. We need to set those defaults on
# to set those defaults on the existing stanzas and XML # the existing stanzas and XML so that they will compare
# so that they will compare correctly. # correctly.
default_stanza = stanza_class() default_stanza = stanza_class()
if defaults is None: if defaults is None:
known_defaults = { known_defaults = {
@ -264,9 +262,9 @@ class SleekTest(unittest.TestCase):
value = default_stanza.xml.attrib[interface] value = default_stanza.xml.attrib[interface]
xml.attrib[interface] = value xml.attrib[interface] = value
values = stanza2.getStanzaValues() values = stanza2.values
stanza3 = stanza_class() stanza3 = stanza_class()
stanza3.setStanzaValues(values) stanza3.values = values
debug = "Three methods for creating stanzas do not match.\n" debug = "Three methods for creating stanzas do not match.\n"
debug += "Given XML:\n%s\n" % tostring(xml) debug += "Given XML:\n%s\n" % tostring(xml)
@ -416,8 +414,7 @@ class SleekTest(unittest.TestCase):
'id', 'stanzapath', 'xpath', and 'mask'. 'id', 'stanzapath', 'xpath', and 'mask'.
Defaults to the value of self.match_method. Defaults to the value of self.match_method.
use_values -- Indicates if stanza comparisons should test using use_values -- Indicates if stanza comparisons should test using
getStanzaValues() and setStanzaValues(). stanza.values. Defaults to True.
Defaults to True.
timeout -- Time to wait in seconds for data to be received by timeout -- Time to wait in seconds for data to be received by
a live connection. a live connection.
""" """

View file

@ -0,0 +1,4 @@
try:
from collections import OrderedDict
except:
from sleekxmpp.thirdparty.ordereddict import OrderedDict

View file

@ -14,6 +14,7 @@ from xml.etree import cElementTree as ET
from sleekxmpp.xmlstream import JID from sleekxmpp.xmlstream import JID
from sleekxmpp.xmlstream.tostring import tostring from sleekxmpp.xmlstream.tostring import tostring
from sleekxmpp.thirdparty import OrderedDict
log = logging.getLogger(__name__) log = logging.getLogger(__name__)
@ -23,17 +24,23 @@ log = logging.getLogger(__name__)
XML_TYPE = type(ET.Element('xml')) XML_TYPE = type(ET.Element('xml'))
def register_stanza_plugin(stanza, plugin): def register_stanza_plugin(stanza, plugin, iterable=False):
""" """
Associate a stanza object as a plugin for another stanza. Associate a stanza object as a plugin for another stanza.
Arguments: Arguments:
stanza -- The class of the parent stanza. stanza -- The class of the parent stanza.
plugin -- The class of the plugin stanza. plugin -- The class of the plugin stanza.
iterable -- Indicates if the plugin stanza
should be included in the parent
stanza's iterable 'substanzas'
interface results.
""" """
tag = "{%s}%s" % (plugin.namespace, plugin.name) tag = "{%s}%s" % (plugin.namespace, plugin.name)
stanza.plugin_attrib_map[plugin.plugin_attrib] = plugin stanza.plugin_attrib_map[plugin.plugin_attrib] = plugin
stanza.plugin_tag_map[tag] = plugin stanza.plugin_tag_map[tag] = plugin
if iterable:
stanza.plugin_iterables.add(plugin)
# To maintain backwards compatibility for now, preserve the camel case name. # To maintain backwards compatibility for now, preserve the camel case name.
@ -95,10 +102,22 @@ class ElementBase(object):
>>> message['custom']['useful_thing'] = 'foo' >>> message['custom']['useful_thing'] = 'foo'
If a plugin provides an interface that is the same as the plugin's If a plugin provides an interface that is the same as the plugin's
plugin_attrib value, then the plugin's interface may be accessed plugin_attrib value, then the plugin's interface may be assigned
directly from the parent stanza, as so: directly from the parent stanza, as shown below, but retrieving
information will require all interfaces to be used, as so:
>>> message['custom'] = 'bar' # Same as using message['custom']['custom'] >>> message['custom'] = 'bar' # Same as using message['custom']['custom']
>>> message['custom']['custom'] # Must use all interfaces
'bar'
If the plugin sets the value is_extension = True, then both setting
and getting an interface value that is the same as the plugin's
plugin_attrib value will work, as so:
>>> message['custom'] = 'bar' # Using is_extension=True
>>> message['custom']
'bar'
Class Attributes: Class Attributes:
name -- The name of the stanza's main element. name -- The name of the stanza's main element.
@ -108,14 +127,23 @@ class ElementBase(object):
sub_interfaces -- A subset of the set of interfaces which map sub_interfaces -- A subset of the set of interfaces which map
to subelements instead of attributes. to subelements instead of attributes.
subitem -- A set of stanza classes which are allowed to subitem -- A set of stanza classes which are allowed to
be added as substanzas. be added as substanzas. Deprecated version
of plugin_iterables.
types -- A set of generic type attribute values. types -- A set of generic type attribute values.
tag -- The namespaced name of the stanza's root
element. Example: "{foo_ns}bar"
plugin_attrib -- The interface name that the stanza uses to be plugin_attrib -- The interface name that the stanza uses to be
accessed as a plugin from another stanza. accessed as a plugin from another stanza.
plugin_attrib_map -- A mapping of plugin attribute names with the plugin_attrib_map -- A mapping of plugin attribute names with the
associated plugin stanza classes. associated plugin stanza classes.
plugin_iterables -- A set of stanza classes which are allowed to
be added as substanzas.
plugin_tag_map -- A mapping of plugin stanza tag names with plugin_tag_map -- A mapping of plugin stanza tag names with
the associated plugin stanza classes. the associated plugin stanza classes.
is_extension -- When True, allows the stanza to provide one
additional interface to the parent stanza,
extending the interfaces supported by the
parent. Defaults to False.
xml_ns -- The XML namespace, xml_ns -- The XML namespace,
http://www.w3.org/XML/1998/namespace, http://www.w3.org/XML/1998/namespace,
for use with xml:lang values. for use with xml:lang values.
@ -128,6 +156,10 @@ class ElementBase(object):
values -- A dictionary of the stanza's interfaces values -- A dictionary of the stanza's interfaces
and interface values, including plugins. and interface values, including plugins.
Class Methods
tag_name -- Return the namespaced version of the stanza's
root element's name.
Methods: Methods:
setup -- Initialize the stanza's XML contents. setup -- Initialize the stanza's XML contents.
enable -- Instantiate a stanza plugin. enable -- Instantiate a stanza plugin.
@ -160,6 +192,7 @@ class ElementBase(object):
appendxml -- Add XML content to the stanza. appendxml -- Add XML content to the stanza.
pop -- Remove a substanza. pop -- Remove a substanza.
next -- Return the next iterable substanza. next -- Return the next iterable substanza.
clear -- Reset the stanza's XML contents.
_fix_ns -- Apply the stanza's namespace to non-namespaced _fix_ns -- Apply the stanza's namespace to non-namespaced
elements in an XPath expression. elements in an XPath expression.
""" """
@ -171,8 +204,10 @@ class ElementBase(object):
types = set(('get', 'set', 'error', None, 'unavailable', 'normal', 'chat')) types = set(('get', 'set', 'error', None, 'unavailable', 'normal', 'chat'))
sub_interfaces = tuple() sub_interfaces = tuple()
plugin_attrib_map = {} plugin_attrib_map = {}
plugin_iterables = set()
plugin_tag_map = {} plugin_tag_map = {}
subitem = None subitem = None
is_extension = False
xml_ns = 'http://www.w3.org/XML/1998/namespace' xml_ns = 'http://www.w3.org/XML/1998/namespace'
def __init__(self, xml=None, parent=None): def __init__(self, xml=None, parent=None):
@ -196,9 +231,10 @@ class ElementBase(object):
self.setStanzaValues = self._set_stanza_values self.setStanzaValues = self._set_stanza_values
self.xml = xml self.xml = xml
self.plugins = {} self.plugins = OrderedDict()
self.iterables = [] self.iterables = []
self._index = 0 self._index = 0
self.tag = self.tag_name()
if parent is None: if parent is None:
self.parent = None self.parent = None
else: else:
@ -218,6 +254,8 @@ class ElementBase(object):
self.plugins[plugin.plugin_attrib] = plugin(child, self) self.plugins[plugin.plugin_attrib] = plugin(child, self)
if self.subitem is not None: if self.subitem is not None:
for sub in self.subitem: for sub in self.subitem:
self.plugin_iterables.add(sub)
for sub in self.plugin_iterables:
if child.tag == "{%s}%s" % (sub.namespace, sub.name): if child.tag == "{%s}%s" % (sub.namespace, sub.name):
self.iterables.append(sub(child, self)) self.iterables.append(sub(child, self))
break break
@ -287,14 +325,12 @@ class ElementBase(object):
for interface in self.interfaces: for interface in self.interfaces:
values[interface] = self[interface] values[interface] = self[interface]
for plugin, stanza in self.plugins.items(): for plugin, stanza in self.plugins.items():
values[plugin] = stanza._get_stanza_values() values[plugin] = stanza.values
if self.iterables: if self.iterables:
iterables = [] iterables = []
for stanza in self.iterables: for stanza in self.iterables:
iterables.append(stanza._get_stanza_values()) iterables.append(stanza.values)
iterables[-1].update({ iterables[-1]['__childtag__'] = stanza.tag
'__childtag__': "{%s}%s" % (stanza.namespace,
stanza.name)})
values['substanzas'] = iterables values['substanzas'] = iterables
return values return values
@ -318,7 +354,7 @@ class ElementBase(object):
subclass.name) subclass.name)
if subdict['__childtag__'] == child_tag: if subdict['__childtag__'] == child_tag:
sub = subclass(parent=self) sub = subclass(parent=self)
sub._set_stanza_values(subdict) sub.values = subdict
self.iterables.append(sub) self.iterables.append(sub)
break break
elif interface in self.interfaces: elif interface in self.interfaces:
@ -326,7 +362,7 @@ class ElementBase(object):
elif interface in self.plugin_attrib_map: elif interface in self.plugin_attrib_map:
if interface not in self.plugins: if interface not in self.plugins:
self.init_plugin(interface) self.init_plugin(interface)
self.plugins[interface]._set_stanza_values(value) self.plugins[interface].values = value
return self return self
def __getitem__(self, attrib): def __getitem__(self, attrib):
@ -371,6 +407,8 @@ class ElementBase(object):
elif attrib in self.plugin_attrib_map: elif attrib in self.plugin_attrib_map:
if attrib not in self.plugins: if attrib not in self.plugins:
self.init_plugin(attrib) self.init_plugin(attrib)
if self.plugins[attrib].is_extension:
return self.plugins[attrib][attrib]
return self.plugins[attrib] return self.plugins[attrib]
else: else:
return '' return ''
@ -467,8 +505,13 @@ class ElementBase(object):
elif attrib in self.plugin_attrib_map: elif attrib in self.plugin_attrib_map:
if attrib in self.plugins: if attrib in self.plugins:
xml = self.plugins[attrib].xml xml = self.plugins[attrib].xml
if self.plugins[attrib].is_extension:
del self.plugins[attrib][attrib]
del self.plugins[attrib] del self.plugins[attrib]
try:
self.xml.remove(xml) self.xml.remove(xml)
except:
pass
return self return self
def _set_attr(self, name, value): def _set_attr(self, name, value):
@ -790,6 +833,28 @@ class ElementBase(object):
""" """
return self.__next__() return self.__next__()
def clear(self):
"""
Remove all XML element contents and plugins.
Any attribute values will be preserved.
"""
for child in self.xml.getchildren():
self.xml.remove(child)
for plugin in list(self.plugins.keys()):
del self.plugins[plugin]
return self
@classmethod
def tag_name(cls):
"""
Return the namespaced name of the stanza's root element.
For example, for the stanza <foo xmlns="bar" />,
stanza.tag would return "{bar}foo".
"""
return "{%s}%s" % (cls.namespace, cls.name)
@property @property
def attrib(self): def attrib(self):
""" """
@ -862,13 +927,13 @@ class ElementBase(object):
return False return False
# Check that this stanza is a superset of the other stanza. # Check that this stanza is a superset of the other stanza.
values = self._get_stanza_values() values = self.values
for key in other.keys(): for key in other.keys():
if key not in values or values[key] != other[key]: if key not in values or values[key] != other[key]:
return False return False
# Check that the other stanza is a superset of this stanza. # Check that the other stanza is a superset of this stanza.
values = other._get_stanza_values() values = other.values
for key in self.keys(): for key in self.keys():
if key not in values or values[key] != self[key]: if key not in values or values[key] != self[key]:
return False return False
@ -972,7 +1037,6 @@ class StanzaBase(ElementBase):
Attributes: Attributes:
stream -- The XMLStream instance that will handle sending this stanza. stream -- The XMLStream instance that will handle sending this stanza.
tag -- The namespaced version of the stanza's name.
Methods: Methods:
set_type -- Set the type of the stanza. set_type -- Set the type of the stanza.
@ -983,7 +1047,6 @@ class StanzaBase(ElementBase):
get_payload -- Return the stanza's XML contents. get_payload -- Return the stanza's XML contents.
set_payload -- Append to the stanza's XML contents. set_payload -- Append to the stanza's XML contents.
del_payload -- Remove the stanza's XML contents. del_payload -- Remove the stanza's XML contents.
clear -- Reset the stanza's XML contents.
reply -- Reset the stanza and modify the 'to' and 'from' reply -- Reset the stanza and modify the 'to' and 'from'
attributes to prepare for sending a reply. attributes to prepare for sending a reply.
error -- Set the stanza's type to 'error'. error -- Set the stanza's type to 'error'.
@ -1098,18 +1161,6 @@ class StanzaBase(ElementBase):
self.clear() self.clear()
return self return self
def clear(self):
"""
Remove all XML element contents and plugins.
Any attribute values will be preserved.
"""
for child in self.xml.getchildren():
self.xml.remove(child)
for plugin in list(self.plugins.keys()):
del self.plugins[plugin]
return self
def reply(self): def reply(self):
""" """
Reset the stanza and swap its 'from' and 'to' attributes to prepare Reset the stanza and swap its 'from' and 'to' attributes to prepare

View file

@ -292,6 +292,7 @@ class XMLStream(object):
return True return True
except Socket.error as serr: except Socket.error as serr:
error_msg = "Could not connect to %s:%s. Socket Error #%s: %s" error_msg = "Could not connect to %s:%s. Socket Error #%s: %s"
self.event('socket_error', serr)
log.error(error_msg % (self.address[0], self.address[1], log.error(error_msg % (self.address[0], self.address[1],
serr.errno, serr.strerror)) serr.errno, serr.strerror))
time.sleep(1) time.sleep(1)
@ -327,7 +328,7 @@ class XMLStream(object):
self.filesocket.close() self.filesocket.close()
self.socket.shutdown(Socket.SHUT_RDWR) self.socket.shutdown(Socket.SHUT_RDWR)
except Socket.error as serr: except Socket.error as serr:
pass self.event('socket_error', serr)
finally: finally:
#clear your application state #clear your application state
self.event("disconnected", direct=True) self.event("disconnected", direct=True)
@ -734,7 +735,8 @@ class XMLStream(object):
except SystemExit: except SystemExit:
log.debug("SystemExit in _process") log.debug("SystemExit in _process")
self.stop.set() self.stop.set()
except Socket.error: except Socket.error as serr:
self.event('socket_error', serr)
log.exception('Socket Error') log.exception('Socket Error')
except: except:
if not self.stop.isSet(): if not self.stop.isSet():
@ -800,7 +802,8 @@ class XMLStream(object):
default_ns = self.default_ns default_ns = self.default_ns
stanza_type = StanzaBase stanza_type = StanzaBase
for stanza_class in self.__root_stanza: for stanza_class in self.__root_stanza:
if xml.tag == "{%s}%s" % (default_ns, stanza_class.name): if xml.tag == "{%s}%s" % (default_ns, stanza_class.name) or \
xml.tag == stanza_class.tag_name():
stanza_type = stanza_class stanza_type = stanza_class
break break
stanza = stanza_type(self, xml) stanza = stanza_type(self, xml)
@ -825,7 +828,8 @@ class XMLStream(object):
# stanza type applies, a generic StanzaBase stanza will be used. # stanza type applies, a generic StanzaBase stanza will be used.
stanza_type = StanzaBase stanza_type = StanzaBase
for stanza_class in self.__root_stanza: for stanza_class in self.__root_stanza:
if xml.tag == "{%s}%s" % (self.default_ns, stanza_class.name): if xml.tag == "{%s}%s" % (self.default_ns, stanza_class.name) or \
xml.tag == stanza_class.tag_name():
stanza_type = stanza_class stanza_type = stanza_class
break break
stanza = stanza_type(self, xml) stanza = stanza_type(self, xml)
@ -899,7 +903,7 @@ class XMLStream(object):
args[0].exception(e) args[0].exception(e)
elif etype == 'schedule': elif etype == 'schedule':
try: try:
log.debug(args) log.debug('Scheduled event: %s' % args)
handler(*args[0]) handler(*args[0])
except: except:
log.exception('Error processing scheduled task') log.exception('Error processing scheduled task')

View file

@ -49,7 +49,7 @@ class TestMessageStanzas(SleekTest):
msg['nick']['nick'] = 'A nickname!' msg['nick']['nick'] = 'A nickname!'
self.check(msg, """ self.check(msg, """
<message> <message>
<nick xmlns="http://jabber.org/nick/nick">A nickname!</nick> <nick xmlns="http://jabber.org/protocol/nick">A nickname!</nick>
</message> </message>
""") """)

View file

@ -59,7 +59,7 @@ class TestPresenceStanzas(SleekTest):
p['nick']['nick'] = 'A nickname!' p['nick']['nick'] = 'A nickname!'
self.check(p, """ self.check(p, """
<presence> <presence>
<nick xmlns="http://jabber.org/nick/nick">A nickname!</nick> <nick xmlns="http://jabber.org/protocol/nick">A nickname!</nick>
</presence> </presence>
""") """)

185
todo1.0
View file

@ -1,123 +1,62 @@
ElementBase sub_items not subitem? Plugins:
0004
*XMPP needs to use JID class instead of lots of fields. PEP8
Stream/Unit tests
BaseXMPP set_jid, makeIqQuery, getjidresource, getjidbare not needed Fix serialization issue
Use OrderedDict for fields/values
Why CamelCase and underscore_names? Document semantics. 0009
Review contribution from dannmartens
conn_tests and sleekxmpp/tests and sleekxmpp/xmlstresm/test.* -> convert to either unit tests, or at least put in same place 0012
PEP8
Update setup.py - github url, version # Documentation
Stream/Unit tests
scheduler needs unit tests 0030
Done
ClientXMPP stream:features handler should use new state machine 0033
PEP8
Write stream tests for startls, features, etc. Documentation
Stream/Unit tests
0045
PEP8
-- PEP8 - all files Documentation
Stream/Unit tests
Need to use spaces 0050
Review replacement in github.com/legastero/adhoc
Docstrings are lacking. Need to document attributes and return values. 0059
Done
Organize imports 0060
PEP8
Use absolute, not relative imports Documentation
Stream/Unit tests
Fix one-liner if statements 0078
Will require new stream features handling, see stream_features branch.
Line length limit of 79 characters PEP8
Documentation
Stream/Unit tests
0085
-- Plugins PEP8
Documentation
--- xep_0004 Stream/Unit tests
0086
Need more unit tests PEP8
Documentation
--- xep_0009 Consider any simplifications.
0092
Need stanza objects Done
0128
Need unit tests Needs complete rewrite to work with new 0030 plugin.
0199
--- xep_0045 PEP8
Documentation
Need to use stanza objects Stream/Unit tests
Needs to use scheduler instead of its own thread.
A few TODO comments for checking roles and using defaults 0202
PEP8
Need unit tests Documentation
Stream/Unit tests
--- xep_0050 0249
Review, minor cleanup
Need unit tests gmail_notify
PEP8
Need stanza objects - use new xep_0004 Documentation
Stream/Unit tests
--- xep_0060
Need unit tests
Need to use existing stanza objects
--- xep_0078
Is it useful still?
Need stanza objects/unit tests
--- xep_0086
Is there a way to automate setting error codes?
Seems like this should be part of the error stanza by default
Use stanza objects
--- xep_0092
Stanza objects
Unit tests
--- xep_0199
Stanza objects
Unit tests
Clean commented code
Use the new scheduler
-- Documentation
Document the Zen/Tao/Whatever of SleekXMPP to explain design goals and decisions
Write architecture description
XMPP:TDG needs to be rewritten.
Need to update docs that reference old JID attributes of sleekxmpp objects
Page describing new JID class
Message page needs updating
Iq page needs to be written
Make guides to go with example.py and component_example.py
Page on xmlstream.matchers
Page on xmlstream.handlers, especially waiters
Page on using xmlstream.scheduler