From 093644ffbd6708121150c92359bce60408f924bb Mon Sep 17 00:00:00 2001 From: Nathan Fritz Date: Tue, 5 Jan 2010 21:56:48 +0000 Subject: * major stanza improvements * raise XMPPError in handler to reply with error stanza * started work on pubsub stanzas --- sleekxmpp/basexmpp.py | 5 ++ sleekxmpp/exceptions.py | 8 ++ sleekxmpp/plugins/stanza_pubsub.py | 177 +++++++++++++++++++++++++++++++++++++ sleekxmpp/plugins/xep_0004.py | 2 +- sleekxmpp/plugins/xep_0060.py | 3 +- sleekxmpp/stanza/iq.py | 18 ++-- sleekxmpp/stanza/message.py | 11 +-- sleekxmpp/stanza/presence.py | 12 +-- sleekxmpp/stanza/rootstanza.py | 25 ++++++ sleekxmpp/xmlstream/stanzabase.py | 22 +++-- sleekxmpp/xmlstream/xmlstream.py | 4 +- 11 files changed, 244 insertions(+), 43 deletions(-) create mode 100644 sleekxmpp/exceptions.py create mode 100644 sleekxmpp/plugins/stanza_pubsub.py create mode 100644 sleekxmpp/stanza/rootstanza.py diff --git a/sleekxmpp/basexmpp.py b/sleekxmpp/basexmpp.py index 8f32c8f5..dd77bfb0 100644 --- a/sleekxmpp/basexmpp.py +++ b/sleekxmpp/basexmpp.py @@ -18,6 +18,8 @@ Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA """ from __future__ import with_statement + + from xml.etree import cElementTree as ET from . xmlstream.xmlstream import XMLStream from . xmlstream.matcher.xmlmask import MatchXMLMask @@ -32,6 +34,7 @@ from . stanza.presence import Presence from . stanza.roster import Roster from . stanza.nick import Nick from . stanza.htmlim import HTMLIM +from . stanza.error import Error import logging import threading @@ -91,6 +94,8 @@ class basexmpp(object): """Register a plugin not in plugins.__init__.__all__ but in the plugins directory.""" # discover relative "path" to the plugins module from the main app, and import it. + # TODO: + # gross, this probably isn't necessary anymore, especially for an installed module __import__("%s.%s" % (globals()['plugins'].__name__, plugin)) # init the plugin class self.plugin[plugin] = getattr(getattr(plugins, plugin), plugin)(self, pconfig) # eek diff --git a/sleekxmpp/exceptions.py b/sleekxmpp/exceptions.py new file mode 100644 index 00000000..9df51700 --- /dev/null +++ b/sleekxmpp/exceptions.py @@ -0,0 +1,8 @@ +class XMPPError(Exception): + def __init__(self, condition='undefined-condition', text=None, etype=None, extension=None, extension_ns=None, extension_args=None): + self.condition = condition + self.text = text + self.etype = etype + self.extension = extension + self.extension_ns = extension_ns + self.extension_args = extension_args diff --git a/sleekxmpp/plugins/stanza_pubsub.py b/sleekxmpp/plugins/stanza_pubsub.py new file mode 100644 index 00000000..b407b799 --- /dev/null +++ b/sleekxmpp/plugins/stanza_pubsub.py @@ -0,0 +1,177 @@ +from .. xmlstream.stanzabase import ElementBase, ET +from .. stanza.iq import Iq +from .. basexmpp import basexmpp +from .. xmlstream.xmlstream import XMLStream +from . import xep_0004 + + +def stanzaPlugin(stanza, plugin): + stanza.plugin_attrib_map[plugin.plugin_attrib] = plugin + stanza.plugin_tag_map["{%s}%s" % (plugin.namespace, plugin.name)] = plugin + +class Pubsub(ElementBase): + namespace = 'http://jabber.org/protocol/pubsub' + name = 'pubsub' + plugin_attrib = 'pubsub' + interfaces = set(( + 'create', + 'configure', + 'subscribe', + 'options', + 'default', + 'items', + 'publish', + 'retract', + 'subscription', + 'subscriptions', + 'unsubscribe', + )) + +stanzaPlugin(Iq, Pubsub) + +class Affiliations(ElementBase): + namespace = 'http://jabber.org/protocol/pubsub' + name = 'affiliations' + plugin_attrib = 'affiliations' + interfaces = set(tuple()) + + def __init__(self, *args, **kwargs): + ElementBase.__init__(self, *args, **kwargs) + self.affiliations = [] + self.idx = 0 + + def __iter__(self): + self.idx = 0 + return self + + def __next__(self): + self.idx += 1 + if self.idx + 1 > len(self.affilations): + self.idx = 0 + raise StopIteration + return self.affiliations[self.idx] + + def __len__(self): + return len(self.affiliations) + + def append(self, affiliation): + if not isinstance(affiliation, Affiliation): + raise TypeError + self.xml.append(affiliation.xml) + return self.affiliations.append(affiliation) + + def pop(self, idx=0): + aff = self.affiliations.pop(idx) + self.xml.remove(aff.xml) + return aff + + def find(self, affilation): + return self.affilations.find(affiliation) + +stanzaPlugin(Pubsub, Affiliations) + +class Affiliation(ElementBase): + namespace = 'http://jabber.org/protocol/pubsub' + name = 'affiliation' + plugin_attrib = name + interfaces = set(('node', 'affiliation')) + +class Items(ElementBase): + namespace = 'http://jabber.org/protocol/pubsub' + name = 'items' + plugin_attrib = 'items' + interfaces = set(tuple()) + + def __init__(self, *args, **kwargs): + ElementBase.__init__(self, *args, **kwargs) + self.items = [] + self.idx = 0 + + def __iter__(self): + self.idx = 0 + return self + + def __next__(self): + self.idx += 1 + if self.idx + 1 > len(self.items): + self.idx = 0 + raise StopIteration + return self.items[self.idx] + + def __len__(self): + return len(self.items) + + def append(self, item): + if not isinstance(item, Item): + raise TypeError + self.xml.append(item.xml) + return self.items.append(item) + + def pop(self, idx=0): + aff = self.items.pop(idx) + self.xml.remove(aff.xml) + return aff + + def find(self, item): + return self.items.find(item) + +stanzaPlugin(Pubsub, Items) + +class Item(ElementBase): + namespace = 'http://jabber.org/protocol/pubsub' + name = 'affiliation' + plugin_attrib = name + interfaces = set(('node', 'affiliation')) +class DefaultConfig(ElementBase): + namespace = 'http://jabber.org/protocol/pubsub' + name = 'default' + plugin_attrib = 'defaultconfig' + interfaces = set(('node', 'type', 'config')) + + def __init__(self, *args, **kwargs): + ElementBase.__init__(self, *args, **kwargs) + + def getConfig(self): + config = self.xml.find('{jabber:x:data}x') + form = xep_0004.Form() + if config is not None: + form.fromXML(config) + return form + + def setConfig(self, value): + self.xml.append(value.getXML()) + return self + + def delConfig(self): + config = self.xml.find('{jabber:x:data}x') + self.xml.remove(config) + +stanzaPlugin(Pubsub, DefaultConfig) + +iq = Iq() +aff1 = Affiliation() +aff1['node'] = 'testnode' +aff1['affiliation'] = 'owner' +aff2 = Affiliation() +aff2['node'] = 'testnode2' +aff2['affiliation'] = 'publisher' +iq['pubsub']['affiliations'].append(aff1) +iq['pubsub']['affiliations'].append(aff2) +print(iq) +iq['pubsub']['affiliations'].pop(0) +print(iq) + +iq = Iq() +iq['pubsub']['defaultconfig'] +print(iq) + +class OwnerAffiliations(Affiliations): + pass + +class OwnerAffiation(Affiliation): + namespace = 'http://jabber.org/protocol/pubsub#owner' + interfaces = set(('node', 'affiliation', 'jid')) + +class PubSubOwner(ElementBase): + namespace = 'http://jabber.org/protocol/pubsub#owner' + nick = 'pubsubowner' diff --git a/sleekxmpp/plugins/xep_0004.py b/sleekxmpp/plugins/xep_0004.py index abd5ceef..ec859252 100644 --- a/sleekxmpp/plugins/xep_0004.py +++ b/sleekxmpp/plugins/xep_0004.py @@ -48,7 +48,7 @@ class xep_0004(base.base_plugin): return object def buildForm(self, xml): - form = Form(xml.attrib['type']) + form = Form(ftype=xml.attrib['type']) form.fromXML(xml) return form diff --git a/sleekxmpp/plugins/xep_0060.py b/sleekxmpp/plugins/xep_0060.py index 734dd3da..837d0ad4 100644 --- a/sleekxmpp/plugins/xep_0060.py +++ b/sleekxmpp/plugins/xep_0060.py @@ -1,7 +1,8 @@ from __future__ import with_statement from . import base import logging -from xml.etree import cElementTree as ET +#from xml.etree import cElementTree as ET +from .. xmlstream.stanzabase import ElementBase, ET class xep_0060(base.base_plugin): """ diff --git a/sleekxmpp/stanza/iq.py b/sleekxmpp/stanza/iq.py index a9c9c4be..cec0f8bc 100644 --- a/sleekxmpp/stanza/iq.py +++ b/sleekxmpp/stanza/iq.py @@ -3,8 +3,9 @@ from xml.etree import cElementTree as ET from . error import Error from .. xmlstream.handler.waiter import Waiter from .. xmlstream.matcher.id import MatcherId +from . rootstanza import RootStanza -class Iq(StanzaBase): +class Iq(RootStanza): interfaces = set(('type', 'to', 'from', 'id','query')) types = set(('get', 'result', 'set', 'error')) name = 'iq' @@ -13,13 +14,10 @@ class Iq(StanzaBase): def __init__(self, *args, **kwargs): StanzaBase.__init__(self, *args, **kwargs) if self['id'] == '': - self['id'] = self.stream.getNewId() - - def exception(self, text): - self.reply() - self['error']['condition'] = 'undefined-condition' - self['error']['text'] = text - self.send() + if self.stream is not None: + self['id'] = self.stream.getNewId() + else: + self['id'] = '0' def unhandled(self): self.reply() @@ -84,7 +82,3 @@ class Iq(StanzaBase): return waitfor.wait(timeout) else: return StanzaBase.send(self) - - -Iq.plugin_attrib_map['error'] = Error -Iq.plugin_tag_map["{%s}%s" % (Error.namespace, Error.name)] = Error diff --git a/sleekxmpp/stanza/message.py b/sleekxmpp/stanza/message.py index d75421c8..c8d54f13 100644 --- a/sleekxmpp/stanza/message.py +++ b/sleekxmpp/stanza/message.py @@ -1,8 +1,9 @@ from .. xmlstream.stanzabase import StanzaBase from xml.etree import cElementTree as ET from . error import Error +from . rootstanza import RootStanza -class Message(StanzaBase): +class Message(RootStanza): interfaces = set(('type', 'to', 'from', 'id', 'body', 'subject')) types = set((None, 'normal', 'chat', 'headline', 'error', 'groupchat')) sub_interfaces = set(('body', 'subject')) @@ -27,11 +28,3 @@ class Message(StanzaBase): self['body'] = body return self - def exception(self, text): - self.reply() - self['error']['condition'] = 'undefined-condition' - self['error']['text'] = text - self.send() - -Message.plugin_attrib_map['error'] = Error -Message.plugin_tag_map["{%s}%s" % (Error.namespace, Error.name)] = Error diff --git a/sleekxmpp/stanza/presence.py b/sleekxmpp/stanza/presence.py index cb6bd6ce..6a8247c5 100644 --- a/sleekxmpp/stanza/presence.py +++ b/sleekxmpp/stanza/presence.py @@ -1,8 +1,9 @@ from .. xmlstream.stanzabase import StanzaBase from xml.etree import cElementTree as ET from . error import Error +from . rootstanza import RootStanza -class Presence(StanzaBase): +class Presence(RootStanza): interfaces = set(('type', 'to', 'from', 'id', 'status', 'priority')) types = set(('available', 'unavailable', 'error', 'probe', 'subscribe', 'subscribed', 'unsubscribe', 'unsubscribed')) showtypes = set(('dnd', 'ffc', 'xa', 'away')) @@ -52,12 +53,3 @@ class Presence(StanzaBase): elif self['type'] == 'subscribe': self['type'] = 'subscribed' return StanzaBase.reply(self) - - def exception(self, text): - self.reply() - self['error']['condition'] = 'undefined-condition' - self['error']['text'] = text - self.send() - -Presence.plugin_attrib_map['error'] = Error -Presence.plugin_tag_map["{%s}%s" % (Error.namespace, Error.name)] = Error diff --git a/sleekxmpp/stanza/rootstanza.py b/sleekxmpp/stanza/rootstanza.py new file mode 100644 index 00000000..72ba5ca9 --- /dev/null +++ b/sleekxmpp/stanza/rootstanza.py @@ -0,0 +1,25 @@ +from .. xmlstream.stanzabase import StanzaBase +from xml.etree import cElementTree as ET +from . error import Error +from .. exceptions import XMPPError +import traceback + +class RootStanza(StanzaBase): + + def exception(self, e): #called when a handler raises an exception + self.reply() + if isinstance(e, XMPPError): # we raised this deliberately + self['error']['condition'] = e.condition + self['error']['text'] = e.text + if e.extension is not None: # extended error tag + extxml = ET.Element("{%s}%s" % (e.extension_ns, e.extension), e.extension_args) + self['error'].xml.append(extxml) + self['error']['type'] = e.etype + else: # we probably didn't raise this on purpose, so send back a traceback + self['error']['condition'] = 'undefined-condition' + self['error']['text'] = traceback.format_tb(e.__traceback__) + self.send() + +# all jabber:client root stanzas should have the error plugin +RootStanza.plugin_attrib_map['error'] = Error +RootStanza.plugin_tag_map["{%s}%s" % (Error.namespace, Error.name)] = Error diff --git a/sleekxmpp/xmlstream/stanzabase.py b/sleekxmpp/xmlstream/stanzabase.py index d941b8cb..30714dc0 100644 --- a/sleekxmpp/xmlstream/stanzabase.py +++ b/sleekxmpp/xmlstream/stanzabase.py @@ -1,5 +1,6 @@ from xml.etree import cElementTree as ET import logging +import traceback class JID(object): def __init__(self, jid): @@ -31,6 +32,7 @@ class ElementBase(object): plugin_tag_map = {} def __init__(self, xml=None, parent=None): + self.attrib = self # backwards compatibility hack self.parent = parent self.xml = xml self.plugins = {} @@ -42,6 +44,9 @@ class ElementBase(object): def match(self, xml): return xml.tag == self.tag + def find(self, xpath): # for backwards compatiblity, expose elementtree interface + return self.xml.find(xpath) + def setup(self, xml=None): if self.xml is None: self.xml = xml @@ -183,9 +188,8 @@ class StanzaBase(ElementBase): types = set(('get', 'set', 'error', None, 'unavailable', 'normal', 'chat')) sub_interfaces = tuple() - def __init__(self, stream, xml=None, stype=None, sto=None, sfrom=None, sid=None): + def __init__(self, stream=None, xml=None, stype=None, sto=None, sfrom=None, sid=None): self.stream = stream - self.namespace = stream.default_ns ElementBase.__init__(self, xml) if stype is not None: self['type'] = stype @@ -193,7 +197,9 @@ class StanzaBase(ElementBase): self['to'] = sto if sfrom is not None: self['from'] = sfrom - self.tag = "{%s}%s" % (self.stream.default_ns, self.name) + if stream is not None: + self.namespace = stream.default_ns + self.tag = "{%s}%s" % (self.namespace, self.name) def setType(self, value): if value in self.types: @@ -240,8 +246,8 @@ class StanzaBase(ElementBase): def unhandled(self): pass - def exception(self, text): - logging.error(text) + def exception(self, e): + logging.error(traceback.format_tb(e)) def send(self): self.stream.sendRaw(str(self)) @@ -257,13 +263,13 @@ class StanzaBase(ElementBase): else: ixmlns = '' nsbuffer = '' - if xmlns != ixmlns and ixmlns != '' and ixmlns != self.stream.default_ns: - if ixmlns in self.stream.namespace_map: + if xmlns != ixmlns and ixmlns != '' and ixmlns != self.namespace: + if self.stream is not None and ixmlns in self.stream.namespace_map: if self.stream.namespace_map[ixmlns] != '': itag = "%s:%s" % (self.stream.namespace_map[ixmlns], itag) else: nsbuffer = """ xmlns="%s\"""" % ixmlns - if ixmlns not in (xmlns, self.namespace): + if ixmlns not in ('', xmlns, self.namespace): nsbuffer = """ xmlns="%s\"""" % ixmlns newoutput.append("<%s" % itag) newoutput.append(nsbuffer) diff --git a/sleekxmpp/xmlstream/xmlstream.py b/sleekxmpp/xmlstream/xmlstream.py index ee884504..cf61ba31 100644 --- a/sleekxmpp/xmlstream/xmlstream.py +++ b/sleekxmpp/xmlstream/xmlstream.py @@ -281,8 +281,8 @@ class XMLStream(object): if etype == 'stanza': try: handler.run(args[0]) - except: - args[0].exception(traceback.format_exc()) + except Exception as e: + args[0].exception(e) elif etype == 'sched': try: handler.run(*args) -- cgit v1.2.3