XMPPError exceptions can keep a stanza's contents.

This allows exceptions to include the original
content of a stanza in the error response by including
the parameter clear=False when raising the exception.
This commit is contained in:
Lance Stout 2011-02-11 15:20:26 -05:00
parent c4b1212c44
commit 0d32638379
7 changed files with 71 additions and 12 deletions

View file

@ -21,7 +21,8 @@ class XMPPError(Exception):
""" """
def __init__(self, condition='undefined-condition', text=None, etype=None, def __init__(self, condition='undefined-condition', text=None, etype=None,
extension=None, extension_ns=None, extension_args=None): extension=None, extension_ns=None, extension_args=None,
clear=True):
""" """
Create a new XMPPError exception. Create a new XMPPError exception.
@ -37,6 +38,9 @@ class XMPPError(Exception):
extension_args -- Content and attributes for the extension extension_args -- Content and attributes for the extension
element. Same as the additional arguments to element. Same as the additional arguments to
the ET.Element constructor. the ET.Element constructor.
clear -- Indicates if the stanza's contents should be
removed before replying with an error.
Defaults to True.
""" """
if extension_args is None: if extension_args is None:
extension_args = {} extension_args = {}
@ -44,6 +48,7 @@ class XMPPError(Exception):
self.condition = condition self.condition = condition
self.text = text self.text = text
self.etype = etype self.etype = etype
self.clear = clear
self.extension = extension self.extension = extension
self.extension_ns = extension_ns self.extension_ns = extension_ns
self.extension_args = extension_args self.extension_args = extension_args

View file

@ -144,7 +144,7 @@ class Iq(RootStanza):
self.xml.remove(child) self.xml.remove(child)
return self return self
def reply(self): def reply(self, clear=True):
""" """
Send a reply <iq> stanza. Send a reply <iq> stanza.
@ -152,9 +152,13 @@ class Iq(RootStanza):
Sets the 'type' to 'result' in addition to the default Sets the 'type' to 'result' in addition to the default
StanzaBase.reply behavior. StanzaBase.reply behavior.
Arguments:
clear -- Indicates if existing content should be
removed before replying. Defaults to True.
""" """
self['type'] = 'result' self['type'] = 'result'
StanzaBase.reply(self) StanzaBase.reply(self, clear)
return self return self
def send(self, block=True, timeout=None, callback=None): def send(self, block=True, timeout=None, callback=None):

View file

@ -104,7 +104,7 @@ class Message(RootStanza):
self['type'] = 'normal' self['type'] = 'normal'
return self return self
def reply(self, body=None): def reply(self, body=None, clear=True):
""" """
Create a message reply. Create a message reply.
@ -114,7 +114,9 @@ class Message(RootStanza):
adds a message body if one is given. adds a message body if one is given.
Arguments: Arguments:
body -- Optional text content for the message. body -- Optional text content for the message.
clear -- Indicates if existing content should be removed
before replying. Defaults to True.
""" """
StanzaBase.reply(self) StanzaBase.reply(self)
if self['type'] == 'groupchat': if self['type'] == 'groupchat':

View file

@ -173,14 +173,18 @@ class Presence(RootStanza):
# The priority is not a number: we consider it 0 as a default # The priority is not a number: we consider it 0 as a default
return 0 return 0
def reply(self): def reply(self, clear=True):
""" """
Set the appropriate presence reply type. Set the appropriate presence reply type.
Overrides StanzaBase.reply. Overrides StanzaBase.reply.
Arguments:
clear -- Indicates if the stanza contents should be removed
before replying. Defaults to True.
""" """
if self['type'] == 'unsubscribe': if self['type'] == 'unsubscribe':
self['type'] = 'unsubscribed' self['type'] = 'unsubscribed'
elif self['type'] == 'subscribe': elif self['type'] == 'subscribe':
self['type'] = 'subscribed' self['type'] = 'subscribed'
return StanzaBase.reply(self) return StanzaBase.reply(self, clear)

View file

@ -43,8 +43,8 @@ class RootStanza(StanzaBase):
Arguments: Arguments:
e -- Exception object e -- Exception object
""" """
self.reply()
if isinstance(e, XMPPError): if isinstance(e, XMPPError):
self.reply(clear=e.clear)
# We raised this deliberately # We raised this deliberately
self['error']['condition'] = e.condition self['error']['condition'] = e.condition
self['error']['text'] = e.text self['error']['text'] = e.text
@ -56,6 +56,7 @@ class RootStanza(StanzaBase):
self['error']['type'] = e.etype self['error']['type'] = e.etype
self.send() self.send()
else: else:
self.reply()
# We probably didn't raise this on purpose, so send an error stanza # We probably didn't raise this on purpose, so send an error stanza
self['error']['condition'] = 'undefined-condition' self['error']['condition'] = 'undefined-condition'
self['error']['text'] = "SleekXMPP got into trouble." self['error']['text'] = "SleekXMPP got into trouble."

View file

@ -1161,12 +1161,17 @@ class StanzaBase(ElementBase):
self.clear() self.clear()
return self return self
def reply(self): def reply(self, clear=True):
""" """
Reset the stanza and swap its 'from' and 'to' attributes to prepare Swap the 'from' and 'to' attributes to prepare the stanza for
for sending a reply stanza. sending a reply. If clear=True, then also remove the stanza's
contents to make room for the reply content.
For client streams, the 'from' attribute is removed. For client streams, the 'from' attribute is removed.
Arguments:
clear -- Indicates if the stanza's contents should be
removed. Defaults to True
""" """
# if it's a component, use from # if it's a component, use from
if self.stream and hasattr(self.stream, "is_component") and \ if self.stream and hasattr(self.stream, "is_component") and \
@ -1175,7 +1180,8 @@ class StanzaBase(ElementBase):
else: else:
self['to'] = self['from'] self['to'] = self['from']
del self['from'] del self['from']
self.clear() if clear:
self.clear()
return self return self
def error(self): def error(self):

View file

@ -1,5 +1,7 @@
import sys import sys
import sleekxmpp import sleekxmpp
from sleekxmpp.xmlstream.matcher import MatchXPath
from sleekxmpp.xmlstream.handler import Callback
from sleekxmpp.exceptions import XMPPError from sleekxmpp.exceptions import XMPPError
from sleekxmpp.test import * from sleekxmpp.test import *
@ -46,6 +48,41 @@ class TestStreamExceptions(SleekTest):
</message> </message>
""", use_values=False) """, use_values=False)
def testIqErrorException(self):
"""Test using error exceptions with Iq stanzas."""
def handle_iq(iq):
raise XMPPError(condition='feature-not-implemented',
text="We don't do things that way here.",
etype='cancel',
clear=False)
self.stream_start()
self.xmpp.register_handler(
Callback(
'Test Iq',
MatchXPath('{%s}iq/{test}query' % self.xmpp.default_ns),
handle_iq))
self.recv("""
<iq type="get" id="0">
<query xmlns="test" />
</iq>
""")
self.send("""
<iq type="error" id="0">
<query xmlns="test" />
<error type="cancel">
<feature-not-implemented
xmlns="urn:ietf:params:xml:ns:xmpp-stanzas" />
<text xmlns="urn:ietf:params:xml:ns:xmpp-stanzas">
We don&apos;t do things that way here.
</text>
</error>
</iq>
""", use_values=False)
def testThreadedXMPPErrorException(self): def testThreadedXMPPErrorException(self):
"""Test raising an XMPPError exception in a threaded handler.""" """Test raising an XMPPError exception in a threaded handler."""