mirror of
https://github.com/correl/SleekXMPP.git
synced 2024-11-24 03:00:15 +00:00
Updated, cleaned, and documented Iq stanza class. Also added unit tests.
This commit is contained in:
parent
1da3e5b35e
commit
cbed8029ba
2 changed files with 257 additions and 67 deletions
|
@ -5,73 +5,170 @@
|
||||||
|
|
||||||
See the file LICENSE for copying permission.
|
See the file LICENSE for copying permission.
|
||||||
"""
|
"""
|
||||||
from .. xmlstream.stanzabase import StanzaBase
|
|
||||||
from xml.etree import cElementTree as ET
|
from sleekxmpp.stanza import Error
|
||||||
from . error import Error
|
from sleekxmpp.stanza.rootstanza import RootStanza
|
||||||
from .. xmlstream.handler.waiter import Waiter
|
from sleekxmpp.xmlstream import RESPONSE_TIMEOUT
|
||||||
from .. xmlstream.matcher.id import MatcherId
|
from sleekxmpp.xmlstream.stanzabase import StanzaBase, ET
|
||||||
from . rootstanza import RootStanza
|
from sleekxmpp.xmlstream.handler import Waiter
|
||||||
|
from sleekxmpp.xmlstream.matcher import MatcherId
|
||||||
|
|
||||||
|
|
||||||
class Iq(RootStanza):
|
class Iq(RootStanza):
|
||||||
interfaces = set(('type', 'to', 'from', 'id','query'))
|
|
||||||
types = set(('get', 'result', 'set', 'error'))
|
|
||||||
name = 'iq'
|
|
||||||
plugin_attrib = name
|
|
||||||
namespace = 'jabber:client'
|
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
"""
|
||||||
StanzaBase.__init__(self, *args, **kwargs)
|
XMPP <iq> stanzas, or info/query stanzas, are XMPP's method of
|
||||||
if self['id'] == '':
|
requesting and modifying information, similar to HTTP's GET and
|
||||||
if self.stream is not None:
|
POST methods.
|
||||||
self['id'] = self.stream.getNewId()
|
|
||||||
else:
|
Each <iq> stanza must have an 'id' value which associates the
|
||||||
self['id'] = '0'
|
stanza with the response stanza. XMPP entities must always
|
||||||
|
be given a response <iq> stanza with a type of 'result' after
|
||||||
def unhandled(self):
|
sending a stanza of type 'get' or 'set'.
|
||||||
if self['type'] in ('get', 'set'):
|
|
||||||
self.reply()
|
Most uses cases for <iq> stanzas will involve adding a <query>
|
||||||
self['error']['condition'] = 'feature-not-implemented'
|
element whose namespace indicates the type of information
|
||||||
self['error']['text'] = 'No handlers registered for this request.'
|
desired. However, some custom XMPP applications use <iq> stanzas
|
||||||
self.send()
|
as a carrier stanza for an application-specific protocol instead.
|
||||||
|
|
||||||
def setPayload(self, value):
|
Example <iq> Stanzas:
|
||||||
self.clear()
|
<iq to="user@example.com" type="get" id="314">
|
||||||
StanzaBase.setPayload(self, value)
|
<query xmlns="http://jabber.org/protocol/disco#items" />
|
||||||
return self
|
</iq>
|
||||||
|
|
||||||
def setQuery(self, value):
|
<iq to="user@localhost" type="result" id="17">
|
||||||
query = self.xml.find("{%s}query" % value)
|
<query xmlns='jabber:iq:roster'>
|
||||||
if query is None and value:
|
<item jid='otheruser@example.net'
|
||||||
self.clear()
|
name='John Doe'
|
||||||
query = ET.Element("{%s}query" % value)
|
subscription='both'>
|
||||||
self.xml.append(query)
|
<group>Friends</group>
|
||||||
return self
|
</item>
|
||||||
|
</query>
|
||||||
def getQuery(self):
|
</iq>
|
||||||
for child in self.xml.getchildren():
|
|
||||||
if child.tag.endswith('query'):
|
Stanza Interface:
|
||||||
ns =child.tag.split('}')[0]
|
query -- The namespace of the <query> element if one exists.
|
||||||
if '{' in ns:
|
|
||||||
ns = ns[1:]
|
Methods:
|
||||||
return ns
|
__init__ -- Overrides StanzaBase.__init__.
|
||||||
return ''
|
unhandled -- Send error if there are no handlers.
|
||||||
|
setPayload -- Overrides StanzaBase.setPayload.
|
||||||
def reply(self):
|
setQuery -- Add or modify a <query> element.
|
||||||
self['type'] = 'result'
|
getQuery -- Return the namespace of the <query> element.
|
||||||
StanzaBase.reply(self)
|
delQuery -- Remove the <query> element.
|
||||||
return self
|
reply -- Overrides StanzaBase.reply
|
||||||
|
send -- Overrides StanzaBase.send
|
||||||
def delQuery(self):
|
"""
|
||||||
for child in self.getchildren():
|
|
||||||
if child.tag.endswith('query'):
|
namespace = 'jabber:client'
|
||||||
self.xml.remove(child)
|
name = 'iq'
|
||||||
return self
|
interfaces = set(('type', 'to', 'from', 'id', 'query'))
|
||||||
|
types = set(('get', 'result', 'set', 'error'))
|
||||||
def send(self, block=True, timeout=10):
|
plugin_attrib = name
|
||||||
if block and self['type'] in ('get', 'set'):
|
|
||||||
waitfor = Waiter('IqWait_%s' % self['id'], MatcherId(self['id']))
|
def __init__(self, *args, **kwargs):
|
||||||
self.stream.registerHandler(waitfor)
|
"""
|
||||||
StanzaBase.send(self)
|
Initialize a new <iq> stanza with an 'id' value.
|
||||||
return waitfor.wait(timeout)
|
|
||||||
else:
|
Overrides StanzaBase.__init__.
|
||||||
return StanzaBase.send(self)
|
"""
|
||||||
|
StanzaBase.__init__(self, *args, **kwargs)
|
||||||
|
if self['id'] == '':
|
||||||
|
if self.stream is not None:
|
||||||
|
self['id'] = self.stream.getNewId()
|
||||||
|
else:
|
||||||
|
self['id'] = '0'
|
||||||
|
|
||||||
|
def unhandled(self):
|
||||||
|
"""
|
||||||
|
Send a feature-not-implemented error if the stanza is not handled.
|
||||||
|
|
||||||
|
Overrides StanzaBase.unhandled.
|
||||||
|
"""
|
||||||
|
if self['type'] in ('get', 'set'):
|
||||||
|
self.reply()
|
||||||
|
self['error']['condition'] = 'feature-not-implemented'
|
||||||
|
self['error']['text'] = 'No handlers registered for this request.'
|
||||||
|
self.send()
|
||||||
|
|
||||||
|
def setPayload(self, value):
|
||||||
|
"""
|
||||||
|
Set the XML contents of the <iq> stanza.
|
||||||
|
|
||||||
|
Arguments:
|
||||||
|
value -- An XML object to use as the <iq> stanza's contents
|
||||||
|
"""
|
||||||
|
self.clear()
|
||||||
|
StanzaBase.setPayload(self, value)
|
||||||
|
return self
|
||||||
|
|
||||||
|
def setQuery(self, value):
|
||||||
|
"""
|
||||||
|
Add or modify a <query> element.
|
||||||
|
|
||||||
|
Query elements are differentiated by their namespace.
|
||||||
|
|
||||||
|
Arguments:
|
||||||
|
value -- The namespace of the <query> element.
|
||||||
|
"""
|
||||||
|
query = self.xml.find("{%s}query" % value)
|
||||||
|
if query is None and value:
|
||||||
|
self.clear()
|
||||||
|
query = ET.Element("{%s}query" % value)
|
||||||
|
self.xml.append(query)
|
||||||
|
return self
|
||||||
|
|
||||||
|
def getQuery(self):
|
||||||
|
"""Return the namespace of the <query> element."""
|
||||||
|
for child in self.xml.getchildren():
|
||||||
|
if child.tag.endswith('query'):
|
||||||
|
ns = child.tag.split('}')[0]
|
||||||
|
if '{' in ns:
|
||||||
|
ns = ns[1:]
|
||||||
|
return ns
|
||||||
|
return ''
|
||||||
|
|
||||||
|
def delQuery(self):
|
||||||
|
"""Remove the <query> element."""
|
||||||
|
for child in self.xml.getchildren():
|
||||||
|
if child.tag.endswith('query'):
|
||||||
|
self.xml.remove(child)
|
||||||
|
return self
|
||||||
|
|
||||||
|
def reply(self):
|
||||||
|
"""
|
||||||
|
Send a reply <iq> stanza.
|
||||||
|
|
||||||
|
Overrides StanzaBase.reply
|
||||||
|
|
||||||
|
Sets the 'type' to 'result' in addition to the default
|
||||||
|
StanzaBase.reply behavior.
|
||||||
|
"""
|
||||||
|
self['type'] = 'result'
|
||||||
|
StanzaBase.reply(self)
|
||||||
|
return self
|
||||||
|
|
||||||
|
def send(self, block=True, timeout=RESPONSE_TIMEOUT):
|
||||||
|
"""
|
||||||
|
Send an <iq> stanza over the XML stream.
|
||||||
|
|
||||||
|
The send call can optionally block until a response is received or
|
||||||
|
a timeout occurs. Be aware that using blocking in non-threaded event
|
||||||
|
handlers can drastically impact performance.
|
||||||
|
|
||||||
|
Overrides StanzaBase.send
|
||||||
|
|
||||||
|
Arguments:
|
||||||
|
block -- Specify if the send call 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 exiting the send call if blocking is used.
|
||||||
|
Defaults to sleekxmpp.xmlstream.RESPONSE_TIMEOUT
|
||||||
|
"""
|
||||||
|
if block and self['type'] in ('get', 'set'):
|
||||||
|
waitfor = Waiter('IqWait_%s' % self['id'], MatcherId(self['id']))
|
||||||
|
self.stream.registerHandler(waitfor)
|
||||||
|
StanzaBase.send(self)
|
||||||
|
return waitfor.wait(timeout)
|
||||||
|
else:
|
||||||
|
return StanzaBase.send(self)
|
||||||
|
|
93
tests/test_iqstanzas.py
Normal file
93
tests/test_iqstanzas.py
Normal file
|
@ -0,0 +1,93 @@
|
||||||
|
from sleektest import *
|
||||||
|
from sleekxmpp.xmlstream.stanzabase import ET
|
||||||
|
|
||||||
|
|
||||||
|
class TestIqStanzas(SleekTest):
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
"""Start XML stream for testing."""
|
||||||
|
self.streamStart()
|
||||||
|
|
||||||
|
def tearDown(self):
|
||||||
|
"""Shutdown the XML stream after testing."""
|
||||||
|
self.streamClose()
|
||||||
|
|
||||||
|
def testSetup(self):
|
||||||
|
"""Test initializing default Iq values."""
|
||||||
|
iq = self.Iq()
|
||||||
|
self.checkIq(iq, """
|
||||||
|
<iq id="0" />
|
||||||
|
""")
|
||||||
|
|
||||||
|
def testPayload(self):
|
||||||
|
"""Test setting Iq stanza payload."""
|
||||||
|
iq = self.Iq()
|
||||||
|
iq.setPayload(ET.Element('{test}tester'))
|
||||||
|
self.checkIq(iq, """
|
||||||
|
<iq id="0">
|
||||||
|
<tester xmlns="test" />
|
||||||
|
</iq>
|
||||||
|
""", use_values=False)
|
||||||
|
|
||||||
|
|
||||||
|
def testUnhandled(self):
|
||||||
|
"""Test behavior for Iq.unhandled."""
|
||||||
|
self.streamRecv("""
|
||||||
|
<iq id="test" type="get">
|
||||||
|
<query xmlns="test" />
|
||||||
|
</iq>
|
||||||
|
""")
|
||||||
|
|
||||||
|
iq = self.Iq()
|
||||||
|
iq['id'] = 'test'
|
||||||
|
iq['error']['condition'] = 'feature-not-implemented'
|
||||||
|
iq['error']['text'] = 'No handlers registered for this request.'
|
||||||
|
|
||||||
|
self.streamSendIq(iq, """
|
||||||
|
<iq id="test" type="error">
|
||||||
|
<error type="cancel">
|
||||||
|
<feature-not-implemented xmlns="urn:ietf:params:xml:ns:xmpp-stanzas" />
|
||||||
|
<text xmlns="urn:ietf:params:xml:ns:xmpp-stanzas">
|
||||||
|
No handlers registered for this request.
|
||||||
|
</text>
|
||||||
|
</error>
|
||||||
|
</iq>
|
||||||
|
""")
|
||||||
|
|
||||||
|
def testQuery(self):
|
||||||
|
"""Test modifying query element of Iq stanzas."""
|
||||||
|
iq = self.Iq()
|
||||||
|
|
||||||
|
iq['query'] = 'query_ns'
|
||||||
|
self.checkIq(iq, """
|
||||||
|
<iq id="0">
|
||||||
|
<query xmlns="query_ns" />
|
||||||
|
</iq>
|
||||||
|
""")
|
||||||
|
|
||||||
|
iq['query'] = 'query_ns2'
|
||||||
|
self.checkIq(iq, """
|
||||||
|
<iq id="0">
|
||||||
|
<query xmlns="query_ns2" />
|
||||||
|
</iq>
|
||||||
|
""")
|
||||||
|
|
||||||
|
self.failUnless(iq['query'] == 'query_ns2', "Query namespace doesn't match")
|
||||||
|
|
||||||
|
del iq['query']
|
||||||
|
self.checkIq(iq, """
|
||||||
|
<iq id="0" />
|
||||||
|
""")
|
||||||
|
|
||||||
|
def testReply(self):
|
||||||
|
"""Test setting proper result type in Iq replies."""
|
||||||
|
iq = self.Iq()
|
||||||
|
iq['to'] = 'user@localhost'
|
||||||
|
iq['type'] = 'get'
|
||||||
|
iq.reply()
|
||||||
|
|
||||||
|
self.checkIq(iq, """
|
||||||
|
<iq id="0" type="result" />
|
||||||
|
""")
|
||||||
|
|
||||||
|
suite = unittest.TestLoader().loadTestsFromTestCase(TestIqStanzas)
|
Loading…
Reference in a new issue