diff options
-rwxr-xr-x | conn_tests/testpubsub.py | 2 | ||||
-rw-r--r-- | sleekxmpp/basexmpp.py | 10 | ||||
-rw-r--r-- | sleekxmpp/clientxmpp.py | 4 | ||||
-rw-r--r-- | sleekxmpp/plugins/xep_0030/disco.py | 29 | ||||
-rw-r--r-- | sleekxmpp/plugins/xep_0030/static.py | 5 | ||||
-rw-r--r-- | sleekxmpp/plugins/xep_0045.py | 1 | ||||
-rw-r--r-- | sleekxmpp/plugins/xep_0078.py | 2 | ||||
-rw-r--r-- | sleekxmpp/plugins/xep_0092/version.py | 2 | ||||
-rw-r--r-- | sleekxmpp/plugins/xep_0199.py | 2 | ||||
-rw-r--r-- | sleekxmpp/plugins/xep_0202.py | 8 | ||||
-rw-r--r-- | sleekxmpp/stanza/__init__.py | 1 | ||||
-rw-r--r-- | sleekxmpp/stanza/iq.py | 1 | ||||
-rw-r--r-- | sleekxmpp/stanza/nick.py | 2 | ||||
-rw-r--r-- | sleekxmpp/stanza/stream_error.py | 69 | ||||
-rw-r--r-- | sleekxmpp/test/sleektest.py | 23 | ||||
-rw-r--r-- | sleekxmpp/thirdparty/__init__.py | 4 | ||||
-rw-r--r-- | sleekxmpp/xmlstream/stanzabase.py | 119 | ||||
-rw-r--r-- | sleekxmpp/xmlstream/xmlstream.py | 14 | ||||
-rw-r--r-- | tests/test_stanza_message.py | 2 | ||||
-rw-r--r-- | tests/test_stanza_presence.py | 2 | ||||
-rw-r--r-- | todo1.0 | 185 |
21 files changed, 291 insertions, 196 deletions
diff --git a/conn_tests/testpubsub.py b/conn_tests/testpubsub.py index 24855c90..3aa7200e 100755 --- a/conn_tests/testpubsub.py +++ b/conn_tests/testpubsub.py @@ -33,7 +33,7 @@ class testps(sleekxmpp.ClientXMPP): self.node = "pstestnode_%s" self.pshost = pshost if pshost is None: - self.pshost = self.server + self.pshost = self.boundjid.host self.nodenum = int(nodenum) self.leafnode = self.nodenum + 1 self.collectnode = self.nodenum + 2 diff --git a/sleekxmpp/basexmpp.py b/sleekxmpp/basexmpp.py index 699424ca..bd953afe 100644 --- a/sleekxmpp/basexmpp.py +++ b/sleekxmpp/basexmpp.py @@ -16,7 +16,7 @@ import sleekxmpp from sleekxmpp import plugins 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.nick import Nick from sleekxmpp.stanza.htmlim import HTMLIM @@ -133,6 +133,10 @@ class BaseXMPP(XMLStream): Callback('Presence', MatchXPath("{%s}presence" % self.default_ns), 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._handle_disconnected) @@ -165,6 +169,7 @@ class BaseXMPP(XMLStream): self.register_stanza(Message) self.register_stanza(Iq) self.register_stanza(Presence) + self.register_stanza(StreamError) # Initialize a few default stanza plugins. register_stanza_plugin(Iq, Roster) @@ -606,6 +611,9 @@ class BaseXMPP(XMLStream): """When disconnected, reset the roster""" self.roster = {} + def _handle_stream_error(self, error): + self.event('stream_error', error) + def _handle_message(self, msg): """Process incoming message stanzas.""" self.event('message', msg) diff --git a/sleekxmpp/clientxmpp.py b/sleekxmpp/clientxmpp.py index c08cd648..2554d299 100644 --- a/sleekxmpp/clientxmpp.py +++ b/sleekxmpp/clientxmpp.py @@ -163,11 +163,13 @@ class ClientXMPP(BaseXMPP): log.debug("Since no address is supplied," + \ "attempting SRV lookup.") 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) except (dns.resolver.NXDOMAIN, dns.resolver.NoAnswer): log.debug("No appropriate SRV record found." + \ " Using JID server name.") + except (dns.exception.Timeout,): + log.debug("DNS resolution timed out.") else: # Pick a random server, weighted by priority. diff --git a/sleekxmpp/plugins/xep_0030/disco.py b/sleekxmpp/plugins/xep_0030/disco.py index 6fd4e85f..a976b988 100644 --- a/sleekxmpp/plugins/xep_0030/disco.py +++ b/sleekxmpp/plugins/xep_0030/disco.py @@ -177,7 +177,10 @@ class xep_0030(base_plugin): elif node is None: self._handlers[htype]['jid'][jid] = handler elif jid is None: - jid = self.xmpp.boundjid.full + if self.xmpp.is_component: + jid = self.xmpp.boundjid.full + else: + jid = self.xmpp.boundjid.bare self._handlers[htype]['node'][(jid, node)] = handler else: 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) - 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. @@ -356,10 +359,12 @@ class xep_0030(base_plugin): subnode -- Optional node for the item. ijid -- The JID to modify. """ + if not jid: + jid = self.xmpp.boundjid.full kwargs = {'ijid': jid, 'name': name, '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): """ @@ -500,7 +505,10 @@ class xep_0030(base_plugin): data -- Optional, custom data to pass to the handler. """ if jid is None: - jid = self.xmpp.boundjid.full + if self.xmpp.is_component: + jid = self.xmpp.boundjid.full + else: + jid = self.xmpp.boundjid.bare if node is None: node = '' @@ -526,8 +534,12 @@ class xep_0030(base_plugin): if iq['type'] == 'get': log.debug("Received disco info query from " + \ "<%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', - iq['to'].full, + jid, iq['disco_info']['node'], iq) iq.reply() @@ -552,8 +564,12 @@ class xep_0030(base_plugin): if iq['type'] == 'get': log.debug("Received disco items query from " + \ "<%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', - iq['to'].full, + jid, iq['disco_items']['node']) iq.reply() if items: @@ -590,3 +606,4 @@ class xep_0030(base_plugin): "Using default disco#info feature.") info.add_feature(info.namespace) return info + diff --git a/sleekxmpp/plugins/xep_0030/static.py b/sleekxmpp/plugins/xep_0030/static.py index eff67f02..f957c84c 100644 --- a/sleekxmpp/plugins/xep_0030/static.py +++ b/sleekxmpp/plugins/xep_0030/static.py @@ -247,8 +247,8 @@ class StaticDisco(object): self.add_node(jid, node) self.nodes[(jid, node)]['items'].add_item( data.get('ijid', ''), - node=data.get('inode', None), - name=data.get('name', None)) + node=data.get('inode', ''), + name=data.get('name', '')) def del_item(self, jid, node, data): """ @@ -262,3 +262,4 @@ class StaticDisco(object): self.nodes[(jid, node)]['items'].del_item( data.get('ijid', ''), node=data.get('inode', None)) + diff --git a/sleekxmpp/plugins/xep_0045.py b/sleekxmpp/plugins/xep_0045.py index feec70db..364fbbd9 100644 --- a/sleekxmpp/plugins/xep_0045.py +++ b/sleekxmpp/plugins/xep_0045.py @@ -316,6 +316,7 @@ class xep_0045(base.base_plugin): x = ET.Element('{jabber:x:data}x', type='cancel') query.append(x) iq = self.xmpp.makeIqSet(query) + iq['to'] = room iq.send() def setRoomConfig(self, room, config, ifrom=''): diff --git a/sleekxmpp/plugins/xep_0078.py b/sleekxmpp/plugins/xep_0078.py index d2c81b16..bb6a4632 100644 --- a/sleekxmpp/plugins/xep_0078.py +++ b/sleekxmpp/plugins/xep_0078.py @@ -36,7 +36,7 @@ class xep_0078(base.base_plugin): log.debug("Starting jabber:iq:auth Authentication") auth_request = self.xmpp.makeIqGet() 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.text = self.xmpp.username auth_request_query.append(username) diff --git a/sleekxmpp/plugins/xep_0092/version.py b/sleekxmpp/plugins/xep_0092/version.py index f59f8819..fb3671e4 100644 --- a/sleekxmpp/plugins/xep_0092/version.py +++ b/sleekxmpp/plugins/xep_0092/version.py @@ -84,5 +84,5 @@ class xep_0092(base_plugin): result = iq.send() if result and result['type'] != 'error': - return result['software_version']._get_stanza_values() + return result['software_version'].values return False diff --git a/sleekxmpp/plugins/xep_0199.py b/sleekxmpp/plugins/xep_0199.py index 2e99ae76..16e79e26 100644 --- a/sleekxmpp/plugins/xep_0199.py +++ b/sleekxmpp/plugins/xep_0199.py @@ -33,7 +33,7 @@ class xep_0199(base.base_plugin): def scheduled_ping(self): 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.") self.xmpp.reconnect() diff --git a/sleekxmpp/plugins/xep_0202.py b/sleekxmpp/plugins/xep_0202.py index fe1191ea..3b31c97a 100644 --- a/sleekxmpp/plugins/xep_0202.py +++ b/sleekxmpp/plugins/xep_0202.py @@ -27,10 +27,12 @@ class EntityTime(ElementBase): 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
- def set_tzo(self, tzo): # TODO: support datetime.tzinfo objects?
+ def set_tzo(self, tzo):
if isinstance(tzo, tzinfo):
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
@@ -45,7 +47,7 @@ class EntityTime(ElementBase): # Returns a datetime object instead the string. Is this a good idea?
value = self._get_sub_text('utc')
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:
return datetime.strptime(value, '%Y-%m-%dT%H:%M:%SZ')
diff --git a/sleekxmpp/stanza/__init__.py b/sleekxmpp/stanza/__init__.py index 8302c43d..dbf7b86f 100644 --- a/sleekxmpp/stanza/__init__.py +++ b/sleekxmpp/stanza/__init__.py @@ -8,6 +8,7 @@ from sleekxmpp.stanza.error import Error +from sleekxmpp.stanza.stream_error import StreamError from sleekxmpp.stanza.iq import Iq from sleekxmpp.stanza.message import Message from sleekxmpp.stanza.presence import Presence diff --git a/sleekxmpp/stanza/iq.py b/sleekxmpp/stanza/iq.py index 6388346c..c6aa64d0 100644 --- a/sleekxmpp/stanza/iq.py +++ b/sleekxmpp/stanza/iq.py @@ -224,4 +224,3 @@ class Iq(RootStanza): else: StanzaBase._set_stanza_values(self, values) return self - diff --git a/sleekxmpp/stanza/nick.py b/sleekxmpp/stanza/nick.py index a9243d1a..dce41d14 100644 --- a/sleekxmpp/stanza/nick.py +++ b/sleekxmpp/stanza/nick.py @@ -44,7 +44,7 @@ class Nick(ElementBase): del_nick -- Remove the <nick> element. """ - namespace = 'http://jabber.org/nick/nick' + namespace = 'http://jabber.org/protocol/nick' name = 'nick' plugin_attrib = name interfaces = set(('nick',)) diff --git a/sleekxmpp/stanza/stream_error.py b/sleekxmpp/stanza/stream_error.py new file mode 100644 index 00000000..cf59a7fa --- /dev/null +++ b/sleekxmpp/stanza/stream_error.py @@ -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' diff --git a/sleekxmpp/test/sleektest.py b/sleekxmpp/test/sleektest.py index ce582e3a..b4c912b4 100644 --- a/sleekxmpp/test/sleektest.py +++ b/sleekxmpp/test/sleektest.py @@ -183,8 +183,7 @@ class SleekTest(unittest.TestCase): """ Create and compare several stanza objects to a correct XML string. - If use_values is False, test using getStanzaValues() and - setStanzaValues() will not be used. + If use_values is False, tests using stanza.values will not be used. Some stanzas provide default values for some interfaces, but 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 defaults for the given and generated stanzas to prevent unexpected test failures. - use_values -- Indicates if testing using getStanzaValues() and - setStanzaValues() should be used. Defaults to - True. + use_values -- Indicates if testing using stanza.values should + be used. Defaults to True. """ if method is None and hasattr(self, 'match_method'): method = getattr(self, 'match_method') @@ -242,10 +240,10 @@ class SleekTest(unittest.TestCase): stanza2 = stanza_class(xml=xml) if use_values: - # Using getStanzaValues() and setStanzaValues() will add - # XML for any interface that has a default value. We need - # to set those defaults on the existing stanzas and XML - # so that they will compare correctly. + # Using stanza.values will add XML for any interface that + # has a default value. We need to set those defaults on + # the existing stanzas and XML so that they will compare + # correctly. default_stanza = stanza_class() if defaults is None: known_defaults = { @@ -264,9 +262,9 @@ class SleekTest(unittest.TestCase): value = default_stanza.xml.attrib[interface] xml.attrib[interface] = value - values = stanza2.getStanzaValues() + values = stanza2.values stanza3 = stanza_class() - stanza3.setStanzaValues(values) + stanza3.values = values debug = "Three methods for creating stanzas do not match.\n" debug += "Given XML:\n%s\n" % tostring(xml) @@ -416,8 +414,7 @@ class SleekTest(unittest.TestCase): 'id', 'stanzapath', 'xpath', and 'mask'. Defaults to the value of self.match_method. use_values -- Indicates if stanza comparisons should test using - getStanzaValues() and setStanzaValues(). - Defaults to True. + stanza.values. Defaults to True. timeout -- Time to wait in seconds for data to be received by a live connection. """ diff --git a/sleekxmpp/thirdparty/__init__.py b/sleekxmpp/thirdparty/__init__.py index e69de29b..276ac3cc 100644 --- a/sleekxmpp/thirdparty/__init__.py +++ b/sleekxmpp/thirdparty/__init__.py @@ -0,0 +1,4 @@ +try: + from collections import OrderedDict +except: + from sleekxmpp.thirdparty.ordereddict import OrderedDict diff --git a/sleekxmpp/xmlstream/stanzabase.py b/sleekxmpp/xmlstream/stanzabase.py index 5551d439..3937a7a9 100644 --- a/sleekxmpp/xmlstream/stanzabase.py +++ b/sleekxmpp/xmlstream/stanzabase.py @@ -14,6 +14,7 @@ from xml.etree import cElementTree as ET from sleekxmpp.xmlstream import JID from sleekxmpp.xmlstream.tostring import tostring +from sleekxmpp.thirdparty import OrderedDict log = logging.getLogger(__name__) @@ -23,17 +24,23 @@ log = logging.getLogger(__name__) 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. Arguments: - stanza -- The class of the parent stanza. - plugin -- The class of the plugin stanza. + stanza -- The class of the parent 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) stanza.plugin_attrib_map[plugin.plugin_attrib] = 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. @@ -95,10 +102,22 @@ class ElementBase(object): >>> message['custom']['useful_thing'] = 'foo' 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 - directly from the parent stanza, as so: + plugin_attrib value, then the plugin's interface may be assigned + 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']['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: 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 to subelements instead of attributes. 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. + 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 accessed as a plugin from another stanza. plugin_attrib_map -- A mapping of plugin attribute names with the 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 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, http://www.w3.org/XML/1998/namespace, for use with xml:lang values. @@ -128,6 +156,10 @@ class ElementBase(object): values -- A dictionary of the stanza's interfaces and interface values, including plugins. + Class Methods + tag_name -- Return the namespaced version of the stanza's + root element's name. + Methods: setup -- Initialize the stanza's XML contents. enable -- Instantiate a stanza plugin. @@ -160,6 +192,7 @@ class ElementBase(object): appendxml -- Add XML content to the stanza. pop -- Remove a substanza. next -- Return the next iterable substanza. + clear -- Reset the stanza's XML contents. _fix_ns -- Apply the stanza's namespace to non-namespaced elements in an XPath expression. """ @@ -171,8 +204,10 @@ class ElementBase(object): types = set(('get', 'set', 'error', None, 'unavailable', 'normal', 'chat')) sub_interfaces = tuple() plugin_attrib_map = {} + plugin_iterables = set() plugin_tag_map = {} subitem = None + is_extension = False xml_ns = 'http://www.w3.org/XML/1998/namespace' def __init__(self, xml=None, parent=None): @@ -196,9 +231,10 @@ class ElementBase(object): self.setStanzaValues = self._set_stanza_values self.xml = xml - self.plugins = {} + self.plugins = OrderedDict() self.iterables = [] self._index = 0 + self.tag = self.tag_name() if parent is None: self.parent = None else: @@ -218,9 +254,11 @@ class ElementBase(object): self.plugins[plugin.plugin_attrib] = plugin(child, self) if self.subitem is not None: for sub in self.subitem: - if child.tag == "{%s}%s" % (sub.namespace, sub.name): - self.iterables.append(sub(child, self)) - break + self.plugin_iterables.add(sub) + for sub in self.plugin_iterables: + if child.tag == "{%s}%s" % (sub.namespace, sub.name): + self.iterables.append(sub(child, self)) + break def setup(self, xml=None): """ @@ -287,14 +325,12 @@ class ElementBase(object): for interface in self.interfaces: values[interface] = self[interface] for plugin, stanza in self.plugins.items(): - values[plugin] = stanza._get_stanza_values() + values[plugin] = stanza.values if self.iterables: iterables = [] for stanza in self.iterables: - iterables.append(stanza._get_stanza_values()) - iterables[-1].update({ - '__childtag__': "{%s}%s" % (stanza.namespace, - stanza.name)}) + iterables.append(stanza.values) + iterables[-1]['__childtag__'] = stanza.tag values['substanzas'] = iterables return values @@ -318,7 +354,7 @@ class ElementBase(object): subclass.name) if subdict['__childtag__'] == child_tag: sub = subclass(parent=self) - sub._set_stanza_values(subdict) + sub.values = subdict self.iterables.append(sub) break elif interface in self.interfaces: @@ -326,7 +362,7 @@ class ElementBase(object): elif interface in self.plugin_attrib_map: if interface not in self.plugins: self.init_plugin(interface) - self.plugins[interface]._set_stanza_values(value) + self.plugins[interface].values = value return self def __getitem__(self, attrib): @@ -371,6 +407,8 @@ class ElementBase(object): elif attrib in self.plugin_attrib_map: if attrib not in self.plugins: self.init_plugin(attrib) + if self.plugins[attrib].is_extension: + return self.plugins[attrib][attrib] return self.plugins[attrib] else: return '' @@ -467,8 +505,13 @@ class ElementBase(object): elif attrib in self.plugin_attrib_map: if attrib in self.plugins: xml = self.plugins[attrib].xml + if self.plugins[attrib].is_extension: + del self.plugins[attrib][attrib] del self.plugins[attrib] - self.xml.remove(xml) + try: + self.xml.remove(xml) + except: + pass return self def _set_attr(self, name, value): @@ -790,6 +833,28 @@ class ElementBase(object): """ 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 def attrib(self): """ @@ -862,13 +927,13 @@ class ElementBase(object): return False # Check that this stanza is a superset of the other stanza. - values = self._get_stanza_values() + values = self.values for key in other.keys(): if key not in values or values[key] != other[key]: return False # Check that the other stanza is a superset of this stanza. - values = other._get_stanza_values() + values = other.values for key in self.keys(): if key not in values or values[key] != self[key]: return False @@ -972,7 +1037,6 @@ class StanzaBase(ElementBase): Attributes: stream -- The XMLStream instance that will handle sending this stanza. - tag -- The namespaced version of the stanza's name. Methods: set_type -- Set the type of the stanza. @@ -983,7 +1047,6 @@ class StanzaBase(ElementBase): get_payload -- Return the stanza's XML contents. set_payload -- Append to 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' attributes to prepare for sending a reply. error -- Set the stanza's type to 'error'. @@ -1098,18 +1161,6 @@ class StanzaBase(ElementBase): self.clear() 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): """ Reset the stanza and swap its 'from' and 'to' attributes to prepare diff --git a/sleekxmpp/xmlstream/xmlstream.py b/sleekxmpp/xmlstream/xmlstream.py index d5c1043b..1cd23fba 100644 --- a/sleekxmpp/xmlstream/xmlstream.py +++ b/sleekxmpp/xmlstream/xmlstream.py @@ -292,6 +292,7 @@ class XMLStream(object): return True except Socket.error as serr: 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], serr.errno, serr.strerror)) time.sleep(1) @@ -327,7 +328,7 @@ class XMLStream(object): self.filesocket.close() self.socket.shutdown(Socket.SHUT_RDWR) except Socket.error as serr: - pass + self.event('socket_error', serr) finally: #clear your application state self.event("disconnected", direct=True) @@ -734,7 +735,8 @@ class XMLStream(object): except SystemExit: log.debug("SystemExit in _process") self.stop.set() - except Socket.error: + except Socket.error as serr: + self.event('socket_error', serr) log.exception('Socket Error') except: if not self.stop.isSet(): @@ -800,7 +802,8 @@ class XMLStream(object): default_ns = self.default_ns stanza_type = StanzaBase 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 break stanza = stanza_type(self, xml) @@ -825,7 +828,8 @@ class XMLStream(object): # stanza type applies, a generic StanzaBase stanza will be used. stanza_type = StanzaBase 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 break stanza = stanza_type(self, xml) @@ -899,7 +903,7 @@ class XMLStream(object): args[0].exception(e) elif etype == 'schedule': try: - log.debug(args) + log.debug('Scheduled event: %s' % args) handler(*args[0]) except: log.exception('Error processing scheduled task') diff --git a/tests/test_stanza_message.py b/tests/test_stanza_message.py index f06b0253..e55971df 100644 --- a/tests/test_stanza_message.py +++ b/tests/test_stanza_message.py @@ -49,7 +49,7 @@ class TestMessageStanzas(SleekTest): msg['nick']['nick'] = 'A nickname!' self.check(msg, """ <message> - <nick xmlns="http://jabber.org/nick/nick">A nickname!</nick> + <nick xmlns="http://jabber.org/protocol/nick">A nickname!</nick> </message> """) diff --git a/tests/test_stanza_presence.py b/tests/test_stanza_presence.py index b8600b6a..2ec43b65 100644 --- a/tests/test_stanza_presence.py +++ b/tests/test_stanza_presence.py @@ -59,7 +59,7 @@ class TestPresenceStanzas(SleekTest): p['nick']['nick'] = 'A nickname!' self.check(p, """ <presence> - <nick xmlns="http://jabber.org/nick/nick">A nickname!</nick> + <nick xmlns="http://jabber.org/protocol/nick">A nickname!</nick> </presence> """) @@ -1,123 +1,62 @@ -ElementBase sub_items not subitem? - -*XMPP needs to use JID class instead of lots of fields. - -BaseXMPP set_jid, makeIqQuery, getjidresource, getjidbare not needed - -Why CamelCase and underscore_names? Document semantics. - -conn_tests and sleekxmpp/tests and sleekxmpp/xmlstresm/test.* -> convert to either unit tests, or at least put in same place - -Update setup.py - github url, version # - -scheduler needs unit tests - -ClientXMPP stream:features handler should use new state machine - -Write stream tests for startls, features, etc. - - - --- PEP8 - all files - -Need to use spaces - -Docstrings are lacking. Need to document attributes and return values. - -Organize imports - -Use absolute, not relative imports - -Fix one-liner if statements - -Line length limit of 79 characters - - - --- Plugins - ---- xep_0004 - -Need more unit tests - ---- xep_0009 - -Need stanza objects - -Need unit tests - ---- xep_0045 - -Need to use stanza objects - -A few TODO comments for checking roles and using defaults - -Need unit tests - ---- xep_0050 - -Need unit tests - -Need stanza objects - use new xep_0004 - ---- 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 +Plugins: + 0004 + PEP8 + Stream/Unit tests + Fix serialization issue + Use OrderedDict for fields/values + 0009 + Review contribution from dannmartens + 0012 + PEP8 + Documentation + Stream/Unit tests + 0030 + Done + 0033 + PEP8 + Documentation + Stream/Unit tests + 0045 + PEP8 + Documentation + Stream/Unit tests + 0050 + Review replacement in github.com/legastero/adhoc + 0059 + Done + 0060 + PEP8 + Documentation + Stream/Unit tests + 0078 + Will require new stream features handling, see stream_features branch. + PEP8 + Documentation + Stream/Unit tests + 0085 + PEP8 + Documentation + Stream/Unit tests + 0086 + PEP8 + Documentation + Consider any simplifications. + 0092 + Done + 0128 + Needs complete rewrite to work with new 0030 plugin. + 0199 + PEP8 + Documentation + Stream/Unit tests + Needs to use scheduler instead of its own thread. + 0202 + PEP8 + Documentation + Stream/Unit tests + 0249 + Review, minor cleanup + gmail_notify + PEP8 + Documentation + Stream/Unit tests |