From d7ba7cc72a65d005ba3702b4254609f6da6a80ca Mon Sep 17 00:00:00 2001 From: Lance Stout Date: Fri, 8 Apr 2011 16:14:22 -0400 Subject: [PATCH 1/9] Use underscore method name. Since camelcase names are aliased to the underscored name at startup, if the underscored version is replaced later, the camelCase name does not reflect the change. --- sleekxmpp/stanza/iq.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sleekxmpp/stanza/iq.py b/sleekxmpp/stanza/iq.py index 2bfbc7b..82ab13e 100644 --- a/sleekxmpp/stanza/iq.py +++ b/sleekxmpp/stanza/iq.py @@ -77,7 +77,7 @@ class Iq(RootStanza): StanzaBase.__init__(self, *args, **kwargs) if self['id'] == '': if self.stream is not None: - self['id'] = self.stream.getNewId() + self['id'] = self.stream.new_id() else: self['id'] = '0' From 87ccd804ff346f3c2aa06b2be9479b1f82817c12 Mon Sep 17 00:00:00 2001 From: Lance Stout Date: Fri, 8 Apr 2011 16:39:39 -0400 Subject: [PATCH 2/9] Add version info. May now use sleekxmpp.__version__ and sleekxmpp.__version_info__. --- sleekxmpp/__init__.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/sleekxmpp/__init__.py b/sleekxmpp/__init__.py index 20f4367..5ad1174 100644 --- a/sleekxmpp/__init__.py +++ b/sleekxmpp/__init__.py @@ -14,3 +14,6 @@ from sleekxmpp.xmlstream.handler import * from sleekxmpp.xmlstream import XMLStream, RestartStream from sleekxmpp.xmlstream.matcher import * from sleekxmpp.xmlstream.stanzabase import StanzaBase, ET + +__version__ = '1.0beta5' +__version_info__ = (1, 0, 0, 'beta5', 0) From 2e1befc8c6e3092b773a8c297bc183823c2eaeed Mon Sep 17 00:00:00 2001 From: Lance Stout Date: Fri, 8 Apr 2011 16:41:18 -0400 Subject: [PATCH 3/9] Make setup.py use sleekxmpp.__version__ --- setup.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 1443e90..1904792 100644 --- a/setup.py +++ b/setup.py @@ -12,6 +12,8 @@ from distutils.core import setup import sys +import sleekxmpp + # if 'cygwin' in sys.platform.lower(): # min_version = '0.6c6' # else: @@ -25,7 +27,7 @@ import sys # # from setuptools import setup, find_packages, Extension, Feature -VERSION = '1.0.0.0' +VERSION = sleekxmpp.__version__ DESCRIPTION = 'SleekXMPP is an elegant Python library for XMPP (aka Jabber, Google Talk, etc).' LONG_DESCRIPTION = """ SleekXMPP is an elegant Python library for XMPP (aka Jabber, Google Talk, etc). From f02b0564e0cf664364cd6694a5df6d588071ec62 Mon Sep 17 00:00:00 2001 From: Lance Stout Date: Fri, 8 Apr 2011 16:51:24 -0400 Subject: [PATCH 4/9] Update tests to reflect XEP-0086 correcting error codes. --- tests/test_stanza_error.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/tests/test_stanza_error.py b/tests/test_stanza_error.py index e1c7d5a..5eecfee 100644 --- a/tests/test_stanza_error.py +++ b/tests/test_stanza_error.py @@ -9,7 +9,7 @@ class TestErrorStanzas(SleekTest): msg.enable('error') self.check(msg, """ - + @@ -22,7 +22,7 @@ class TestErrorStanzas(SleekTest): self.check(msg, """ - + @@ -34,7 +34,7 @@ class TestErrorStanzas(SleekTest): self.check(msg, """ - + @@ -50,7 +50,7 @@ class TestErrorStanzas(SleekTest): self.check(msg, """ - + Error! @@ -66,7 +66,7 @@ class TestErrorStanzas(SleekTest): self.check(msg, """ - + From 1d891858b63c1e3ac10a39920c3e65159cfc8d72 Mon Sep 17 00:00:00 2001 From: Lance Stout Date: Mon, 11 Apr 2011 14:22:32 -0400 Subject: [PATCH 5/9] Mark scheduler thread as a daemon. --- sleekxmpp/xmlstream/scheduler.py | 1 + 1 file changed, 1 insertion(+) diff --git a/sleekxmpp/xmlstream/scheduler.py b/sleekxmpp/xmlstream/scheduler.py index 0e711b4..12deedd 100644 --- a/sleekxmpp/xmlstream/scheduler.py +++ b/sleekxmpp/xmlstream/scheduler.py @@ -132,6 +132,7 @@ class Scheduler(object): if threaded: self.thread = threading.Thread(name='sheduler_process', target=self._process) + self.thread.daemon = True self.thread.start() else: self._process() From 016aac69f6328a2227e1770e78b44ae8e727b11a Mon Sep 17 00:00:00 2001 From: Nathan Fritz Date: Thu, 14 Apr 2011 17:34:33 -0700 Subject: [PATCH 6/9] Pubsub/Unsubscribe was not getting registered --- sleekxmpp/plugins/stanza_pubsub.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/sleekxmpp/plugins/stanza_pubsub.py b/sleekxmpp/plugins/stanza_pubsub.py index 2d809a3..b596453 100644 --- a/sleekxmpp/plugins/stanza_pubsub.py +++ b/sleekxmpp/plugins/stanza_pubsub.py @@ -237,6 +237,8 @@ class Unsubscribe(ElementBase): def getJid(self): return JID(self._getAttr('jid')) +registerStanzaPlugin(Pubsub, Unsubscribe) + class Subscribe(ElementBase): namespace = 'http://jabber.org/protocol/pubsub' name = 'subscribe' From 5399fdd3a95377bfb5bba993f299664557cabb7c Mon Sep 17 00:00:00 2001 From: Lance Stout Date: Tue, 26 Apr 2011 16:32:58 -0400 Subject: [PATCH 7/9] Add support for testing that no stanzas are sent in tests. Use: self.send(None) --- sleekxmpp/test/sleektest.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/sleekxmpp/test/sleektest.py b/sleekxmpp/test/sleektest.py index c5f4889..fd47a87 100644 --- a/sleekxmpp/test/sleektest.py +++ b/sleekxmpp/test/sleektest.py @@ -600,8 +600,13 @@ class SleekTest(unittest.TestCase): Defaults to the value of self.match_method. """ sent = self.xmpp.socket.next_sent(timeout) + if data is None and sent is None: + return + if data is None and sent is not None: + self.fail("Stanza data was sent: %s" % sent) if sent is None: self.fail("No stanza was sent.") + xml = self.parse_xml(sent) self.fix_namespaces(xml, 'jabber:client') sent = self.xmpp._build_stanza(xml, 'jabber:client') From 8e9b3d0760f7cfbce0d480cb61050a9b78746f8c Mon Sep 17 00:00:00 2001 From: Lance Stout Date: Fri, 13 May 2011 15:28:47 -0400 Subject: [PATCH 8/9] Ensure that the XEP-0086 plugin is loaded. Since the XEP-0086 plugin auto adds error code values, it must be reliably loaded or unloaded when certain tests are run so that stanzas may be matched. In this case, we ensure that the plugin is used. --- tests/test_stanza_error.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/tests/test_stanza_error.py b/tests/test_stanza_error.py index 5eecfee..a41bf4b 100644 --- a/tests/test_stanza_error.py +++ b/tests/test_stanza_error.py @@ -3,6 +3,11 @@ from sleekxmpp.test import * class TestErrorStanzas(SleekTest): + def setUp(self): + # Ensure that the XEP-0086 plugin has been loaded. + self.stream_start() + self.stream_close() + def testSetup(self): """Test setting initial values in error stanza.""" msg = self.Message() From 9f1648328f17b608651989606b9cf2636cdcfbec Mon Sep 17 00:00:00 2001 From: Lance Stout Date: Fri, 20 May 2011 12:56:00 -0400 Subject: [PATCH 9/9] Resolve timeout errors for get_roster. See issue #89 Using get_roster will now return the same types of values as Iq.send. If a timeout occurs, then the event 'roster_timeout' will be fired. A successful call to get_roster will also raise the 'roster_received' event. To ensure that the get_roster call was successful, here is a pattern to follow: def __init__(self, ...): ... self.add_event_handler('session_start', self.session_start) self.add_event_handler('roster_timeout', self.roster_timeout) self.add_event_handler('roster_received', self.roster_received) def session_start(self, e): self.send_presence() self.get_roster() def roster_timeout(self, e): # Optionally increase the timeout period self.get_roster(timeout=self.response_timeout * 2) def roster_received(self, iq): # Do stuff, roster has been initialized. ... --- sleekxmpp/clientxmpp.py | 53 ++++++++++++++++++++---- tests/test_stream_roster.py | 81 +++++++++++++++++++++++++++++++++++++ 2 files changed, 126 insertions(+), 8 deletions(-) diff --git a/sleekxmpp/clientxmpp.py b/sleekxmpp/clientxmpp.py index 20cc941..c518a4c 100644 --- a/sleekxmpp/clientxmpp.py +++ b/sleekxmpp/clientxmpp.py @@ -206,7 +206,8 @@ class ClientXMPP(BaseXMPP): pointer, breaker)) - def update_roster(self, jid, name=None, subscription=None, groups=[]): + def update_roster(self, jid, name=None, subscription=None, groups=[], + block=True, timeout=None, callback=None): """ Add or change a roster item. @@ -217,12 +218,24 @@ class ClientXMPP(BaseXMPP): 'to', 'from', 'both', or 'none'. If set to 'remove', the entry will be deleted. groups -- The roster groups that contain this item. + block -- Specify if the roster request will block + until a response is received, or a timeout + occurs. Defaults to True. + timeout -- The length of time (in seconds) to wait + for a response before continuing if blocking + is used. Defaults to self.response_timeout. + callback -- Optional reference to a stream handler function. + Will be executed when the roster is received. + Implies block=False. """ - iq = self.Iq()._set_stanza_values({'type': 'set'}) + iq = self.Iq() + iq['type'] = 'set' iq['roster']['items'] = {jid: {'name': name, 'subscription': subscription, 'groups': groups}} - response = iq.send() + response = iq.send(block, timeout, callback) + if response in [False, None]: + return response return response['type'] == 'result' def del_roster_item(self, jid): @@ -235,11 +248,33 @@ class ClientXMPP(BaseXMPP): """ return self.update_roster(jid, subscription='remove') - def get_roster(self): - """Request the roster from the server.""" - iq = self.Iq()._set_stanza_values({'type': 'get'}).enable('roster') - response = iq.send() - self._handle_roster(response, request=True) + def get_roster(self, block=True, timeout=None, callback=None): + """ + Request the roster from the server. + + Arguments: + block -- Specify if the roster request will block until a + response is received, or a timeout occurs. + Defaults to True. + timeout -- The length of time (in seconds) to wait for a response + before continuing if blocking is used. + Defaults to self.response_timeout. + callback -- Optional reference to a stream handler function. Will + be executed when the roster is received. + Implies block=False. + """ + iq = self.Iq() + iq['type'] = 'get' + iq.enable('roster') + response = iq.send(block, timeout, callback) + + if response == False: + self.event('roster_timeout') + + if response in [False, None] or not isinstance(response, Iq): + return response + else: + return self._handle_roster(response, request=True) def _handle_stream_features(self, features): """ @@ -431,12 +466,14 @@ class ClientXMPP(BaseXMPP): 'presence': {}, 'in_roster': True} self.roster[jid].update(iq['roster']['items'][jid]) + self.event('roster_received', iq) self.event("roster_update", iq) if iq['type'] == 'set': iq.reply() iq.enable('roster') iq.send() + return True # To comply with PEP8, method names now use underscores. diff --git a/tests/test_stream_roster.py b/tests/test_stream_roster.py index 165a8bc..731d114 100644 --- a/tests/test_stream_roster.py +++ b/tests/test_stream_roster.py @@ -16,6 +16,13 @@ class TestStreamRoster(SleekTest): self.stream_start(mode='client') self.failUnless(self.xmpp.roster == {}, "Initial roster not empty.") + events = [] + + def roster_received(iq): + events.append('roster_received') + + self.xmpp.add_event_handler('roster_received', roster_received) + # Since get_roster blocks, we need to run it in a thread. t = threading.Thread(name='get_roster', target=self.xmpp.get_roster) t.start() @@ -41,6 +48,9 @@ class TestStreamRoster(SleekTest): # Wait for get_roster to return. t.join() + # Give the event queue time to process. + time.sleep(.1) + roster = {'user@localhost': {'name': 'User', 'subscription': 'both', 'groups': ['Friends', 'Examples'], @@ -48,11 +58,20 @@ class TestStreamRoster(SleekTest): 'in_roster': True}} self.failUnless(self.xmpp.roster == roster, "Unexpected roster values: %s" % self.xmpp.roster) + self.failUnless('roster_received' in events, + "Roster received event not triggered: %s" % events) def testRosterSet(self): """Test handling pushed roster updates.""" self.stream_start(mode='client') self.failUnless(self.xmpp.roster == {}, "Initial roster not empty.") + events = [] + + def roster_update(e): + events.append('roster_update') + + self.xmpp.add_event_handler('roster_update', roster_update) + self.recv(""" @@ -72,6 +91,9 @@ class TestStreamRoster(SleekTest): """) + # Give the event queue time to process. + time.sleep(.1) + roster = {'user@localhost': {'name': 'User', 'subscription': 'both', 'groups': ['Friends', 'Examples'], @@ -79,7 +101,66 @@ class TestStreamRoster(SleekTest): 'in_roster': True}} self.failUnless(self.xmpp.roster == roster, "Unexpected roster values: %s" % self.xmpp.roster) + self.failUnless('roster_update' in events, + "Roster updated event not triggered: %s" % events) + def testRosterTimeout(self): + """Test handling a timed out roster request.""" + self.stream_start() + events = [] + + def roster_timeout(event): + events.append('roster_timeout') + + self.xmpp.add_event_handler('roster_timeout', roster_timeout) + self.xmpp.get_roster(timeout=0) + + # Give the event queue time to process. + time.sleep(.1) + + self.failUnless(events == ['roster_timeout'], + "Roster timeout event not triggered: %s." % events) + + def testRosterCallback(self): + """Test handling a roster request callback.""" + self.stream_start() + events = [] + + def roster_callback(iq): + events.append('roster_callback') + + # Since get_roster blocks, we need to run it in a thread. + t = threading.Thread(name='get_roster', + target=self.xmpp.get_roster, + kwargs={'callback': roster_callback}) + t.start() + + self.send(""" + + + + """) + self.recv(""" + + + + Friends + Examples + + + + """) + + # Wait for get_roster to return. + t.join() + + # Give the event queue time to process. + time.sleep(.1) + + self.failUnless(events == ['roster_callback'], + "Roster timeout event not triggered: %s." % events)