* major stanza improvements

* raise XMPPError in handler to reply with error stanza
* started work on pubsub stanzas
This commit is contained in:
Nathan Fritz 2010-01-05 21:56:48 +00:00
parent 805afa4bc1
commit 093644ffbd
11 changed files with 244 additions and 43 deletions

View file

@ -18,6 +18,8 @@
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
""" """
from __future__ import with_statement from __future__ import with_statement
from xml.etree import cElementTree as ET from xml.etree import cElementTree as ET
from . xmlstream.xmlstream import XMLStream from . xmlstream.xmlstream import XMLStream
from . xmlstream.matcher.xmlmask import MatchXMLMask from . xmlstream.matcher.xmlmask import MatchXMLMask
@ -32,6 +34,7 @@ from . stanza.presence import Presence
from . stanza.roster import Roster from . stanza.roster import Roster
from . stanza.nick import Nick from . stanza.nick import Nick
from . stanza.htmlim import HTMLIM from . stanza.htmlim import HTMLIM
from . stanza.error import Error
import logging import logging
import threading import threading
@ -91,6 +94,8 @@ class basexmpp(object):
"""Register a plugin not in plugins.__init__.__all__ but in the plugins """Register a plugin not in plugins.__init__.__all__ but in the plugins
directory.""" directory."""
# discover relative "path" to the plugins module from the main app, and import it. # 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)) __import__("%s.%s" % (globals()['plugins'].__name__, plugin))
# init the plugin class # init the plugin class
self.plugin[plugin] = getattr(getattr(plugins, plugin), plugin)(self, pconfig) # eek self.plugin[plugin] = getattr(getattr(plugins, plugin), plugin)(self, pconfig) # eek

8
sleekxmpp/exceptions.py Normal file
View file

@ -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

View file

@ -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'

View file

@ -48,7 +48,7 @@ class xep_0004(base.base_plugin):
return object return object
def buildForm(self, xml): def buildForm(self, xml):
form = Form(xml.attrib['type']) form = Form(ftype=xml.attrib['type'])
form.fromXML(xml) form.fromXML(xml)
return form return form

View file

@ -1,7 +1,8 @@
from __future__ import with_statement from __future__ import with_statement
from . import base from . import base
import logging 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): class xep_0060(base.base_plugin):
""" """

View file

@ -3,8 +3,9 @@ from xml.etree import cElementTree as ET
from . error import Error from . error import Error
from .. xmlstream.handler.waiter import Waiter from .. xmlstream.handler.waiter import Waiter
from .. xmlstream.matcher.id import MatcherId from .. xmlstream.matcher.id import MatcherId
from . rootstanza import RootStanza
class Iq(StanzaBase): class Iq(RootStanza):
interfaces = set(('type', 'to', 'from', 'id','query')) interfaces = set(('type', 'to', 'from', 'id','query'))
types = set(('get', 'result', 'set', 'error')) types = set(('get', 'result', 'set', 'error'))
name = 'iq' name = 'iq'
@ -13,13 +14,10 @@ class Iq(StanzaBase):
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
StanzaBase.__init__(self, *args, **kwargs) StanzaBase.__init__(self, *args, **kwargs)
if self['id'] == '': if self['id'] == '':
self['id'] = self.stream.getNewId() if self.stream is not None:
self['id'] = self.stream.getNewId()
def exception(self, text): else:
self.reply() self['id'] = '0'
self['error']['condition'] = 'undefined-condition'
self['error']['text'] = text
self.send()
def unhandled(self): def unhandled(self):
self.reply() self.reply()
@ -84,7 +82,3 @@ class Iq(StanzaBase):
return waitfor.wait(timeout) return waitfor.wait(timeout)
else: else:
return StanzaBase.send(self) return StanzaBase.send(self)
Iq.plugin_attrib_map['error'] = Error
Iq.plugin_tag_map["{%s}%s" % (Error.namespace, Error.name)] = Error

View file

@ -1,8 +1,9 @@
from .. xmlstream.stanzabase import StanzaBase from .. xmlstream.stanzabase import StanzaBase
from xml.etree import cElementTree as ET from xml.etree import cElementTree as ET
from . error import Error from . error import Error
from . rootstanza import RootStanza
class Message(StanzaBase): class Message(RootStanza):
interfaces = set(('type', 'to', 'from', 'id', 'body', 'subject')) interfaces = set(('type', 'to', 'from', 'id', 'body', 'subject'))
types = set((None, 'normal', 'chat', 'headline', 'error', 'groupchat')) types = set((None, 'normal', 'chat', 'headline', 'error', 'groupchat'))
sub_interfaces = set(('body', 'subject')) sub_interfaces = set(('body', 'subject'))
@ -27,11 +28,3 @@ class Message(StanzaBase):
self['body'] = body self['body'] = body
return self 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

View file

@ -1,8 +1,9 @@
from .. xmlstream.stanzabase import StanzaBase from .. xmlstream.stanzabase import StanzaBase
from xml.etree import cElementTree as ET from xml.etree import cElementTree as ET
from . error import Error from . error import Error
from . rootstanza import RootStanza
class Presence(StanzaBase): class Presence(RootStanza):
interfaces = set(('type', 'to', 'from', 'id', 'status', 'priority')) interfaces = set(('type', 'to', 'from', 'id', 'status', 'priority'))
types = set(('available', 'unavailable', 'error', 'probe', 'subscribe', 'subscribed', 'unsubscribe', 'unsubscribed')) types = set(('available', 'unavailable', 'error', 'probe', 'subscribe', 'subscribed', 'unsubscribe', 'unsubscribed'))
showtypes = set(('dnd', 'ffc', 'xa', 'away')) showtypes = set(('dnd', 'ffc', 'xa', 'away'))
@ -52,12 +53,3 @@ class Presence(StanzaBase):
elif self['type'] == 'subscribe': elif self['type'] == 'subscribe':
self['type'] = 'subscribed' self['type'] = 'subscribed'
return StanzaBase.reply(self) 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

View file

@ -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

View file

@ -1,5 +1,6 @@
from xml.etree import cElementTree as ET from xml.etree import cElementTree as ET
import logging import logging
import traceback
class JID(object): class JID(object):
def __init__(self, jid): def __init__(self, jid):
@ -31,6 +32,7 @@ class ElementBase(object):
plugin_tag_map = {} plugin_tag_map = {}
def __init__(self, xml=None, parent=None): def __init__(self, xml=None, parent=None):
self.attrib = self # backwards compatibility hack
self.parent = parent self.parent = parent
self.xml = xml self.xml = xml
self.plugins = {} self.plugins = {}
@ -42,6 +44,9 @@ class ElementBase(object):
def match(self, xml): def match(self, xml):
return xml.tag == self.tag 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): def setup(self, xml=None):
if self.xml is None: if self.xml is None:
self.xml = xml self.xml = xml
@ -183,9 +188,8 @@ class StanzaBase(ElementBase):
types = set(('get', 'set', 'error', None, 'unavailable', 'normal', 'chat')) types = set(('get', 'set', 'error', None, 'unavailable', 'normal', 'chat'))
sub_interfaces = tuple() 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.stream = stream
self.namespace = stream.default_ns
ElementBase.__init__(self, xml) ElementBase.__init__(self, xml)
if stype is not None: if stype is not None:
self['type'] = stype self['type'] = stype
@ -193,7 +197,9 @@ class StanzaBase(ElementBase):
self['to'] = sto self['to'] = sto
if sfrom is not None: if sfrom is not None:
self['from'] = sfrom 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): def setType(self, value):
if value in self.types: if value in self.types:
@ -240,8 +246,8 @@ class StanzaBase(ElementBase):
def unhandled(self): def unhandled(self):
pass pass
def exception(self, text): def exception(self, e):
logging.error(text) logging.error(traceback.format_tb(e))
def send(self): def send(self):
self.stream.sendRaw(str(self)) self.stream.sendRaw(str(self))
@ -257,13 +263,13 @@ class StanzaBase(ElementBase):
else: else:
ixmlns = '' ixmlns = ''
nsbuffer = '' nsbuffer = ''
if xmlns != ixmlns and ixmlns != '' and ixmlns != self.stream.default_ns: if xmlns != ixmlns and ixmlns != '' and ixmlns != self.namespace:
if ixmlns in self.stream.namespace_map: if self.stream is not None and ixmlns in self.stream.namespace_map:
if self.stream.namespace_map[ixmlns] != '': if self.stream.namespace_map[ixmlns] != '':
itag = "%s:%s" % (self.stream.namespace_map[ixmlns], itag) itag = "%s:%s" % (self.stream.namespace_map[ixmlns], itag)
else: else:
nsbuffer = """ xmlns="%s\"""" % ixmlns nsbuffer = """ xmlns="%s\"""" % ixmlns
if ixmlns not in (xmlns, self.namespace): if ixmlns not in ('', xmlns, self.namespace):
nsbuffer = """ xmlns="%s\"""" % ixmlns nsbuffer = """ xmlns="%s\"""" % ixmlns
newoutput.append("<%s" % itag) newoutput.append("<%s" % itag)
newoutput.append(nsbuffer) newoutput.append(nsbuffer)

View file

@ -281,8 +281,8 @@ class XMLStream(object):
if etype == 'stanza': if etype == 'stanza':
try: try:
handler.run(args[0]) handler.run(args[0])
except: except Exception as e:
args[0].exception(traceback.format_exc()) args[0].exception(e)
elif etype == 'sched': elif etype == 'sched':
try: try:
handler.run(*args) handler.run(*args)