From d7dea0c6ccdd769fb32229e8b719a7556b8e0643 Mon Sep 17 00:00:00 2001 From: Lance Stout Date: Fri, 14 Jan 2011 12:07:25 -0500 Subject: [PATCH 01/21] Add a note for debug statement when running scheduled events. Fixes the intermittent DEBUG ((),) messages that give no explanation. Will now show as: DEBUG Scheduled event: ((), ) --- sleekxmpp/xmlstream/xmlstream.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sleekxmpp/xmlstream/xmlstream.py b/sleekxmpp/xmlstream/xmlstream.py index d5c1043..3cf28ed 100644 --- a/sleekxmpp/xmlstream/xmlstream.py +++ b/sleekxmpp/xmlstream/xmlstream.py @@ -899,7 +899,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') From a2891d760867c00f222ff4323b107378cd7a3a3d Mon Sep 17 00:00:00 2001 From: Lance Stout Date: Sat, 15 Jan 2011 10:08:35 -0500 Subject: [PATCH 02/21] Fix how disco plugin looks up info and items for clients. --- sleekxmpp/plugins/xep_0030/disco.py | 22 ++++++++++++++++++---- 1 file changed, 18 insertions(+), 4 deletions(-) diff --git a/sleekxmpp/plugins/xep_0030/disco.py b/sleekxmpp/plugins/xep_0030/disco.py index 6fd4e85..72dda1d 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 @@ -500,7 +503,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 +532,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 +562,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: From ead3af31351c3468e27307ce1999d325d7f17ca9 Mon Sep 17 00:00:00 2001 From: Lance Stout Date: Sat, 15 Jan 2011 17:15:33 -0500 Subject: [PATCH 03/21] Make it easier to import OrderedDict --- sleekxmpp/thirdparty/__init__.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/sleekxmpp/thirdparty/__init__.py b/sleekxmpp/thirdparty/__init__.py index e69de29..4e9d847 100644 --- a/sleekxmpp/thirdparty/__init__.py +++ b/sleekxmpp/thirdparty/__init__.py @@ -0,0 +1,4 @@ +try: + from ordereddict import OrderedDict +except: + from sleekxmpp.thirdparty.ordereddict import OrderedDict From cb85d4a5297b0eb6cc24052321b38f9f29f62004 Mon Sep 17 00:00:00 2001 From: Lance Stout Date: Sun, 16 Jan 2011 13:07:39 -0500 Subject: [PATCH 04/21] Raise the event 'socket_error' when a socket error occurs. Will be most useful for debugging and responding to failed connection attempts. --- sleekxmpp/xmlstream/xmlstream.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/sleekxmpp/xmlstream/xmlstream.py b/sleekxmpp/xmlstream/xmlstream.py index 3cf28ed..39b10a2 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(): From 2004ddd678fae9af590db2065478d2592bc2530b Mon Sep 17 00:00:00 2001 From: Lance Stout Date: Sun, 16 Jan 2011 13:22:52 -0500 Subject: [PATCH 05/21] Add StreamError stanza and a stream_error event. Note that the stream may automatically attempt to reconnect when a stream error is received. --- sleekxmpp/basexmpp.py | 16 ++++++--- sleekxmpp/stanza/__init__.py | 1 + sleekxmpp/stanza/iq.py | 1 - sleekxmpp/stanza/stream_error.py | 59 ++++++++++++++++++++++++++++++++ 4 files changed, 72 insertions(+), 5 deletions(-) create mode 100644 sleekxmpp/stanza/stream_error.py diff --git a/sleekxmpp/basexmpp.py b/sleekxmpp/basexmpp.py index bc168b8..3cf949a 100644 --- a/sleekxmpp/basexmpp.py +++ b/sleekxmpp/basexmpp.py @@ -15,7 +15,7 @@ import logging import sleekxmpp from sleekxmpp import plugins -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 @@ -128,6 +128,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('presence_subscribe', self._handle_subscribe) @@ -135,9 +139,10 @@ class BaseXMPP(XMLStream): self._handle_disconnected) # Set up the XML stream with XMPP's root stanzas. - self.registerStanza(Message) - self.registerStanza(Iq) - self.registerStanza(Presence) + 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) @@ -579,6 +584,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/stanza/__init__.py b/sleekxmpp/stanza/__init__.py index 8302c43..dbf7b86 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 6388346..c6aa64d 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/stream_error.py b/sleekxmpp/stanza/stream_error.py new file mode 100644 index 0000000..dd0c119 --- /dev/null +++ b/sleekxmpp/stanza/stream_error.py @@ -0,0 +1,59 @@ +""" + 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 ElementBase, ET, register_stanza_plugin + + +class StreamError(Error): + + """ + XMPP stanzas of type 'error' should include an 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 the same as the normal Error stanza, + but with a different namespace. + + Example error stanza: + + + + The item was not found. + + + + Stanza Interface: + code -- The error code used in older XMPP versions. + condition -- The name of the condition element. + text -- Human readable description of the error. + type -- Error type indicating how the error should be handled. + + Attributes: + conditions -- The set of allowable error condition elements. + condition_ns -- The namespace for the condition element. + types -- A set of values indicating how the error + should be treated. + + 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 element. + set_text -- Set the contents of the element. + del_text -- Remove the element. + """ + + namespace = 'http://etherx.jabber.org/streams' From f1db2fc156aabba78e4aa726b441c434d48e9136 Mon Sep 17 00:00:00 2001 From: Lance Stout Date: Wed, 19 Jan 2011 12:08:28 -0500 Subject: [PATCH 06/21] Fix error in disco add_item. None values were not being treated properly. --- sleekxmpp/plugins/xep_0030/disco.py | 7 +++++-- sleekxmpp/plugins/xep_0030/static.py | 5 +++-- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/sleekxmpp/plugins/xep_0030/disco.py b/sleekxmpp/plugins/xep_0030/disco.py index 72dda1d..6746873 100644 --- a/sleekxmpp/plugins/xep_0030/disco.py +++ b/sleekxmpp/plugins/xep_0030/disco.py @@ -345,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. @@ -359,10 +359,12 @@ class xep_0030(base_plugin): subnode -- Optional node for the item. ijid -- The JID to modify. """ + if jid is None: + jid = '' 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): """ @@ -604,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 eff67f0..f957c84 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)) + From d3b1f8c476f42968ac2cc7d7de1631ebd928af0f Mon Sep 17 00:00:00 2001 From: Lance Stout Date: Wed, 19 Jan 2011 16:47:18 -0500 Subject: [PATCH 07/21] Fix namespace for Nick stanza. --- sleekxmpp/stanza/nick.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sleekxmpp/stanza/nick.py b/sleekxmpp/stanza/nick.py index a9243d1..dce41d1 100644 --- a/sleekxmpp/stanza/nick.py +++ b/sleekxmpp/stanza/nick.py @@ -44,7 +44,7 @@ class Nick(ElementBase): del_nick -- Remove the element. """ - namespace = 'http://jabber.org/nick/nick' + namespace = 'http://jabber.org/protocol/nick' name = 'nick' plugin_attrib = name interfaces = set(('nick',)) From acc2d071ac5da8e822bd3c63dda640a81e93deec Mon Sep 17 00:00:00 2001 From: Lance Stout Date: Wed, 19 Jan 2011 17:27:53 -0500 Subject: [PATCH 08/21] Fix disco add_item. If no JID is specified for the item, use xmpp.boundjid.full. --- sleekxmpp/plugins/xep_0030/disco.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/sleekxmpp/plugins/xep_0030/disco.py b/sleekxmpp/plugins/xep_0030/disco.py index 6746873..a976b98 100644 --- a/sleekxmpp/plugins/xep_0030/disco.py +++ b/sleekxmpp/plugins/xep_0030/disco.py @@ -359,8 +359,8 @@ class xep_0030(base_plugin): subnode -- Optional node for the item. ijid -- The JID to modify. """ - if jid is None: - jid = '' + if not jid: + jid = self.xmpp.boundjid.full kwargs = {'ijid': jid, 'name': name, 'inode': subnode} From 897a9ac3335ef42e454aba44346d8702564ab48f Mon Sep 17 00:00:00 2001 From: Florent Le Coz Date: Thu, 20 Jan 2011 04:23:00 +0800 Subject: [PATCH 09/21] Do not traceback when DNS resolution time out. Just log that the resolution timed out, and fall back to the hostname from the JID in this case --- sleekxmpp/clientxmpp.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/sleekxmpp/clientxmpp.py b/sleekxmpp/clientxmpp.py index 734c9a1..b866810 100644 --- a/sleekxmpp/clientxmpp.py +++ b/sleekxmpp/clientxmpp.py @@ -169,6 +169,8 @@ class ClientXMPP(BaseXMPP): 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. From 493df570353fbeff288d6ee61fb0842622443967 Mon Sep 17 00:00:00 2001 From: Lance Stout Date: Wed, 19 Jan 2011 17:49:39 -0500 Subject: [PATCH 10/21] Fix thirdparty imports for Python3 --- sleekxmpp/thirdparty/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sleekxmpp/thirdparty/__init__.py b/sleekxmpp/thirdparty/__init__.py index 4e9d847..276ac3c 100644 --- a/sleekxmpp/thirdparty/__init__.py +++ b/sleekxmpp/thirdparty/__init__.py @@ -1,4 +1,4 @@ try: - from ordereddict import OrderedDict + from collections import OrderedDict except: from sleekxmpp.thirdparty.ordereddict import OrderedDict From f7e7bf601e6bd65cdb85f44c67e05b9ba66b0aeb Mon Sep 17 00:00:00 2001 From: Lance Stout Date: Wed, 19 Jan 2011 19:03:02 -0500 Subject: [PATCH 11/21] Fix tests for Nick stanza. --- tests/test_stanza_message.py | 2 +- tests/test_stanza_presence.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/test_stanza_message.py b/tests/test_stanza_message.py index f06b025..e55971d 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, """ - A nickname! + A nickname! """) diff --git a/tests/test_stanza_presence.py b/tests/test_stanza_presence.py index 8d043d5..f9305a3 100644 --- a/tests/test_stanza_presence.py +++ b/tests/test_stanza_presence.py @@ -58,7 +58,7 @@ class TestPresenceStanzas(SleekTest): p['nick']['nick'] = 'A nickname!' self.check(p, """ - A nickname! + A nickname! """) From da332365d4e97720ac182e0f66b73919d764a555 Mon Sep 17 00:00:00 2001 From: Lance Stout Date: Wed, 19 Jan 2011 19:49:13 -0500 Subject: [PATCH 12/21] Make extending stanza objects nicer. 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("")) >>> i1['foo'] = '3' >>> i1 '3' >>> i1 '' >>> i2 '' >>> i2['foo'] 'bar' >>> del i2['foo'] >>> i2 '' --- sleekxmpp/xmlstream/stanzabase.py | 30 +++++++++++++++++++++++++++--- 1 file changed, 27 insertions(+), 3 deletions(-) diff --git a/sleekxmpp/xmlstream/stanzabase.py b/sleekxmpp/xmlstream/stanzabase.py index 5551d43..2c00866 100644 --- a/sleekxmpp/xmlstream/stanzabase.py +++ b/sleekxmpp/xmlstream/stanzabase.py @@ -95,10 +95,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. @@ -116,6 +128,10 @@ class ElementBase(object): associated plugin stanza classes. 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. @@ -173,6 +189,7 @@ class ElementBase(object): plugin_attrib_map = {} plugin_tag_map = {} subitem = None + is_extension = False xml_ns = 'http://www.w3.org/XML/1998/namespace' def __init__(self, xml=None, parent=None): @@ -371,6 +388,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 +486,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): From c3be6ea0b2141fe86b9aa0c2701e43b72f86ae30 Mon Sep 17 00:00:00 2001 From: Stefan de Konink Date: Sun, 23 Jan 2011 01:47:22 +0800 Subject: [PATCH 13/21] My hunch is that these should also be updated. --- conn_tests/testpubsub.py | 2 +- sleekxmpp/clientxmpp.py | 2 +- sleekxmpp/plugins/xep_0078.py | 2 +- sleekxmpp/plugins/xep_0199.py | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/conn_tests/testpubsub.py b/conn_tests/testpubsub.py index 24855c9..3aa7200 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/clientxmpp.py b/sleekxmpp/clientxmpp.py index b866810..a181398 100644 --- a/sleekxmpp/clientxmpp.py +++ b/sleekxmpp/clientxmpp.py @@ -164,7 +164,7 @@ 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." + \ diff --git a/sleekxmpp/plugins/xep_0078.py b/sleekxmpp/plugins/xep_0078.py index d2c81b1..bb6a463 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_0199.py b/sleekxmpp/plugins/xep_0199.py index 2e99ae7..16e79e2 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() From 4e757c2b56e7c4ded0a3de9e49c550db36da72d2 Mon Sep 17 00:00:00 2001 From: Lance Stout Date: Wed, 26 Jan 2011 10:04:36 -0500 Subject: [PATCH 14/21] Upgraded how subitem works. 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. --- sleekxmpp/xmlstream/stanzabase.py | 26 +++++++++++++++++++------- 1 file changed, 19 insertions(+), 7 deletions(-) diff --git a/sleekxmpp/xmlstream/stanzabase.py b/sleekxmpp/xmlstream/stanzabase.py index 2c00866..764aaca 100644 --- a/sleekxmpp/xmlstream/stanzabase.py +++ b/sleekxmpp/xmlstream/stanzabase.py @@ -23,17 +23,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. @@ -120,12 +126,15 @@ 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. 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 @@ -187,6 +196,7 @@ 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 @@ -235,9 +245,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): """ From 0c8a8314b2e70d4a82b1c199b7e5bc16b494e275 Mon Sep 17 00:00:00 2001 From: Lance Stout Date: Wed, 26 Jan 2011 11:27:41 -0500 Subject: [PATCH 15/21] Cleanup for stanzabase. 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. --- sleekxmpp/plugins/xep_0092/version.py | 2 +- sleekxmpp/test/sleektest.py | 23 +++++----- sleekxmpp/xmlstream/stanzabase.py | 60 +++++++++++++++++---------- 3 files changed, 48 insertions(+), 37 deletions(-) diff --git a/sleekxmpp/plugins/xep_0092/version.py b/sleekxmpp/plugins/xep_0092/version.py index f59f881..fb3671e 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/test/sleektest.py b/sleekxmpp/test/sleektest.py index aa411cd..b5c28fd 100644 --- a/sleekxmpp/test/sleektest.py +++ b/sleekxmpp/test/sleektest.py @@ -157,8 +157,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 @@ -181,9 +180,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') @@ -216,10 +214,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 = { @@ -238,9 +236,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) @@ -390,8 +388,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/xmlstream/stanzabase.py b/sleekxmpp/xmlstream/stanzabase.py index 764aaca..558ab74 100644 --- a/sleekxmpp/xmlstream/stanzabase.py +++ b/sleekxmpp/xmlstream/stanzabase.py @@ -129,6 +129,8 @@ class ElementBase(object): 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 @@ -153,6 +155,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. @@ -185,6 +191,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. """ @@ -226,6 +233,7 @@ class ElementBase(object): self.plugins = {} self.iterables = [] self._index = 0 + self.tag = self.tag_name() if parent is None: self.parent = None else: @@ -316,14 +324,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 @@ -347,7 +353,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: @@ -355,7 +361,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): @@ -826,6 +832,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 , + stanza.tag would return "{bar}foo". + """ + return "{%s}%s" % (cls.namespace, cls.name) + @property def attrib(self): """ @@ -898,13 +926,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 @@ -1008,7 +1036,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. @@ -1019,7 +1046,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'. @@ -1134,18 +1160,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 From b4004cd4d677d6e6c0504d38f7ef0c18db23c71d Mon Sep 17 00:00:00 2001 From: Florent Le Coz Date: Sat, 22 Jan 2011 03:18:04 +0800 Subject: [PATCH 16/21] xep_0045: fix the 'to' value when configuring room --- sleekxmpp/plugins/xep_0045.py | 1 + 1 file changed, 1 insertion(+) diff --git a/sleekxmpp/plugins/xep_0045.py b/sleekxmpp/plugins/xep_0045.py index feec70d..364fbbd 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=''): From 38dc35840e5653ad99ecb5c65fef464b751a32e2 Mon Sep 17 00:00:00 2001 From: Lance Stout Date: Thu, 27 Jan 2011 15:59:50 -0500 Subject: [PATCH 17/21] Recognize stanzas that don't use the default namespace. --- sleekxmpp/xmlstream/xmlstream.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/sleekxmpp/xmlstream/xmlstream.py b/sleekxmpp/xmlstream/xmlstream.py index 39b10a2..1cd23fb 100644 --- a/sleekxmpp/xmlstream/xmlstream.py +++ b/sleekxmpp/xmlstream/xmlstream.py @@ -802,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) @@ -827,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) From 35ef8f909077fa700ebd90e5ccbd3fd71a7c2f7e Mon Sep 17 00:00:00 2001 From: Lance Stout Date: Thu, 27 Jan 2011 16:01:35 -0500 Subject: [PATCH 18/21] Make stanza.plugins an OrderedDict. This allows you to determine the order in which substanzas were added in the original XML. --- sleekxmpp/xmlstream/stanzabase.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/sleekxmpp/xmlstream/stanzabase.py b/sleekxmpp/xmlstream/stanzabase.py index 558ab74..3937a7a 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__) @@ -230,7 +231,7 @@ 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() From 40642b2cd1460678a6cdaa5bf64b93248a11b523 Mon Sep 17 00:00:00 2001 From: Lance Stout Date: Thu, 27 Jan 2011 16:02:57 -0500 Subject: [PATCH 19/21] Make StreamError work properly. Now uses the correct namespaces and condition names. --- sleekxmpp/stanza/stream_error.py | 36 ++++++++++++++++++++------------ 1 file changed, 23 insertions(+), 13 deletions(-) diff --git a/sleekxmpp/stanza/stream_error.py b/sleekxmpp/stanza/stream_error.py index dd0c119..cf59a7f 100644 --- a/sleekxmpp/stanza/stream_error.py +++ b/sleekxmpp/stanza/stream_error.py @@ -7,10 +7,11 @@ """ from sleekxmpp.stanza.error import Error -from sleekxmpp.xmlstream import ElementBase, ET, register_stanza_plugin +from sleekxmpp.xmlstream import StanzaBase, ElementBase, ET +from sleekxmpp.xmlstream import register_stanza_plugin -class StreamError(Error): +class StreamError(Error, StanzaBase): """ XMPP stanzas of type 'error' should include an stanza that @@ -23,28 +24,25 @@ class StreamError(Error): error that occur with the underlying XML stream itself, and not a particular stanza. - Note: The StreamError stanza is the same as the normal Error stanza, - but with a different namespace. + Note: The StreamError stanza is mostly the same as the normal + Error stanza, but with different namespaces and + condition names. Example error stanza: - - - - The item was not found. + + + + XML was not well-formed. - + Stanza Interface: - code -- The error code used in older XMPP versions. condition -- The name of the condition element. text -- Human readable description of the error. - type -- Error type indicating how the error should be handled. Attributes: conditions -- The set of allowable error condition elements. condition_ns -- The namespace for the condition element. - types -- A set of values indicating how the error - should be treated. Methods: setup -- Overrides ElementBase.setup. @@ -57,3 +55,15 @@ class StreamError(Error): """ 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' From 5313338c3ac3350e0c4a524c974021972f10ab94 Mon Sep 17 00:00:00 2001 From: Lance Stout Date: Mon, 31 Jan 2011 15:40:00 -0500 Subject: [PATCH 20/21] Fixes for XEP-0202 --- sleekxmpp/plugins/xep_0202.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/sleekxmpp/plugins/xep_0202.py b/sleekxmpp/plugins/xep_0202.py index fe1191e..3b31c97 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') From 8dbe6f65462ec9b1a0506a00316415996f4d53d8 Mon Sep 17 00:00:00 2001 From: Lance Stout Date: Mon, 31 Jan 2011 15:54:44 -0500 Subject: [PATCH 21/21] Updated todo list for 1.0 release. --- todo1.0 | 185 +++++++++++++++++++------------------------------------- 1 file changed, 62 insertions(+), 123 deletions(-) diff --git a/todo1.0 b/todo1.0 index 191c0e2..3212a31 100644 --- a/todo1.0 +++ b/todo1.0 @@ -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