mirror of
https://github.com/correl/SleekXMPP.git
synced 2024-11-23 19:19:53 +00:00
Finished the update of ElementBase with docs and unit tests.
Corrected bugs in equality comparisons between stanzas.
This commit is contained in:
parent
10298a6eab
commit
d68bc2ba07
2 changed files with 282 additions and 7 deletions
|
@ -34,6 +34,116 @@ def registerStanzaPlugin(stanza, plugin):
|
|||
|
||||
|
||||
class ElementBase(object):
|
||||
|
||||
"""
|
||||
The core of SleekXMPP's stanza XML manipulation and handling is provided
|
||||
by ElementBase. ElementBase wraps XML cElementTree objects and enables
|
||||
access to the XML contents through dictionary syntax, similar in style
|
||||
to the Ruby XMPP library Blather's stanza implementation.
|
||||
|
||||
Stanzas are defined by their name, namespace, and interfaces. For
|
||||
example, a simplistic Message stanza could be defined as:
|
||||
|
||||
>>> class Message(ElementBase):
|
||||
... name = "message"
|
||||
... namespace = "jabber:client"
|
||||
... interfaces = set(('to', 'from', 'type', 'body'))
|
||||
... sub_interfaces = set(('body',))
|
||||
|
||||
The resulting Message stanza's contents may be accessed as so:
|
||||
|
||||
>>> message['to'] = "user@example.com"
|
||||
>>> message['body'] = "Hi!"
|
||||
|
||||
The interface values map to either custom access methods, stanza
|
||||
XML attributes, or (if the interface is also in sub_interfaces) the
|
||||
text contents of a stanza's subelement.
|
||||
|
||||
Custom access methods may be created by adding methods of the
|
||||
form "getInterface", "setInterface", or "delInterface", where
|
||||
"Interface" is the titlecase version of the interface name.
|
||||
|
||||
Stanzas may be extended through the use of plugins. A plugin
|
||||
is simply a stanza that has a plugin_attrib value. For example:
|
||||
|
||||
>>> class MessagePlugin(ElementBase):
|
||||
... name = "custom_plugin"
|
||||
... namespace = "custom"
|
||||
... interfaces = set(('useful_thing', 'custom'))
|
||||
... plugin_attrib = "custom"
|
||||
|
||||
The plugin stanza class must be associated with its intended
|
||||
container stanza by using registerStanzaPlugin as so:
|
||||
|
||||
>>> registerStanzaPlugin(Message, MessagePlugin)
|
||||
|
||||
The plugin may then be accessed as if it were built-in to the parent
|
||||
stanza.
|
||||
|
||||
>>> message['custom']['useful_thing'] = 'foo'
|
||||
|
||||
If a plugin provides an interface that is the same as the plugin's
|
||||
plugin_attrib value, then the plugin's interface may be accessed
|
||||
directly from the parent stanza, as so:
|
||||
|
||||
>>> message['custom'] = 'bar' # Same as using message['custom']['custom']
|
||||
|
||||
Class Attributes:
|
||||
name -- The name of the stanza's main element.
|
||||
namespace -- The namespace of the stanza's main element.
|
||||
interfaces -- A set of attribute and element names that may
|
||||
be accessed using dictionary syntax.
|
||||
sub_interfaces -- A subset of the set of interfaces which map
|
||||
to subelements instead of attributes.
|
||||
subitem -- A set of stanza classes which are allowed to
|
||||
be added as substanzas.
|
||||
types -- A set of generic type attribute values.
|
||||
plugin_attrib -- The interface name that the stanza uses to be
|
||||
accessed as a plugin from another stanza.
|
||||
plugin_attrib_map -- A mapping of plugin attribute names with the
|
||||
associated plugin stanza classes.
|
||||
plugin_tag_map -- A mapping of plugin stanza tag names with
|
||||
the associated plugin stanza classes.
|
||||
|
||||
Instance Attributes:
|
||||
xml -- The stanza's XML contents.
|
||||
parent -- The parent stanza of this stanza.
|
||||
plugins -- A map of enabled plugin names with the
|
||||
initialized plugin stanza objects.
|
||||
|
||||
Methods:
|
||||
setup -- Initialize the stanza's XML contents.
|
||||
enable -- Instantiate a stanza plugin. Alias for initPlugin.
|
||||
initPlugin -- Instantiate a stanza plugin.
|
||||
getStanzaValues -- Return a dictionary of stanza interfaces and
|
||||
their values.
|
||||
setStanzaValues -- Set stanza interface values given a dictionary of
|
||||
interfaces and values.
|
||||
__getitem__ -- Return the value of a stanza interface.
|
||||
__setitem__ -- Set the value of a stanza interface.
|
||||
__delitem__ -- Remove the value of a stanza interface.
|
||||
_setAttr -- Set an attribute value of the main stanza element.
|
||||
_delAttr -- Remove an attribute from the main stanza element.
|
||||
_getAttr -- Return an attribute's value from the main
|
||||
stanza element.
|
||||
_getSubText -- Return the text contents of a subelement.
|
||||
_setSubText -- Set the text contents of a subelement.
|
||||
_delSub -- Remove a subelement.
|
||||
match -- Compare the stanza against an XPath expression.
|
||||
find -- Return subelement matching an XPath expression.
|
||||
findall -- Return subelements matching an XPath expression.
|
||||
get -- Return the value of a stanza interface, with an
|
||||
optional default value.
|
||||
keys -- Return the set of interface names accepted by
|
||||
the stanza.
|
||||
append -- Add XML content or a substanza to the stanza.
|
||||
appendxml -- Add XML content to the stanza.
|
||||
pop -- Remove a substanza.
|
||||
next -- Return the next iterable substanza.
|
||||
_fix_ns -- Apply the stanza's namespace to non-namespaced
|
||||
elements in an XPath expression.
|
||||
"""
|
||||
|
||||
name = 'stanza'
|
||||
plugin_attrib = 'plugin'
|
||||
namespace = 'jabber:client'
|
||||
|
@ -567,7 +677,7 @@ class ElementBase(object):
|
|||
out += [x for x in self.plugins]
|
||||
if self.iterables:
|
||||
out.append('substanzas')
|
||||
return tuple(out)
|
||||
return out
|
||||
|
||||
def append(self, item):
|
||||
"""
|
||||
|
@ -667,12 +777,35 @@ class ElementBase(object):
|
|||
"""
|
||||
if not isinstance(other, ElementBase):
|
||||
return False
|
||||
|
||||
# Check that this stanza is a superset of the other stanza.
|
||||
values = self.getStanzaValues()
|
||||
for key in other:
|
||||
for key in other.keys():
|
||||
if key not in values or values[key] != other[key]:
|
||||
return False
|
||||
|
||||
# Check that the other stanza is a superset of this stanza.
|
||||
values = other.getStanzaValues()
|
||||
for key in self.keys():
|
||||
if key not in values or values[key] != self[key]:
|
||||
return False
|
||||
|
||||
# Both stanzas are supersets of each other, therefore they
|
||||
# must be equal.
|
||||
return True
|
||||
|
||||
def __ne__(self, other):
|
||||
"""
|
||||
Compare the stanza object with another to test for inequality.
|
||||
|
||||
Stanzas are not equal if their interfaces return different values,
|
||||
or if they are not both instances of ElementBase.
|
||||
|
||||
Arguments:
|
||||
other -- The stanza object to compare against.
|
||||
"""
|
||||
return not self.__eq__(other)
|
||||
|
||||
def __bool__(self):
|
||||
"""
|
||||
Stanza objects should be treated as True in boolean contexts.
|
||||
|
|
|
@ -267,7 +267,7 @@ class TestElementBase(SleekTest):
|
|||
|
||||
self.failUnless(stanza._getAttr('bar', 'c') == 'c',
|
||||
"Incorrect default value returned for an unset XML attribute.")
|
||||
|
||||
|
||||
def testGetSubText(self):
|
||||
"""Test retrieving the contents of a sub element."""
|
||||
|
||||
|
@ -287,7 +287,7 @@ class TestElementBase(SleekTest):
|
|||
return self._getSubText("wrapper/bar", default="not found")
|
||||
|
||||
stanza = TestStanza()
|
||||
self.failUnless(stanza['bar'] == 'not found',
|
||||
self.failUnless(stanza['bar'] == 'not found',
|
||||
"Default _getSubText value incorrect.")
|
||||
|
||||
stanza['bar'] = 'found'
|
||||
|
@ -298,7 +298,7 @@ class TestElementBase(SleekTest):
|
|||
</wrapper>
|
||||
</foo>
|
||||
""")
|
||||
self.failUnless(stanza['bar'] == 'found',
|
||||
self.failUnless(stanza['bar'] == 'found',
|
||||
"_getSubText value incorrect: %s." % stanza['bar'])
|
||||
|
||||
def testSubElement(self):
|
||||
|
@ -450,7 +450,7 @@ class TestElementBase(SleekTest):
|
|||
registerStanzaPlugin(TestStanza, TestStanzaPlugin)
|
||||
|
||||
stanza = TestStanza()
|
||||
self.failUnless(stanza.match("foo"),
|
||||
self.failUnless(stanza.match("foo"),
|
||||
"Stanza did not match its own tag name.")
|
||||
|
||||
self.failUnless(stanza.match("{foo}foo"),
|
||||
|
@ -479,6 +479,148 @@ class TestElementBase(SleekTest):
|
|||
|
||||
self.failUnless(stanza.match("foo/{baz}sub"),
|
||||
"Stanza did not match with namespaced substanza.")
|
||||
|
||||
|
||||
def testComparisons(self):
|
||||
"""Test comparing ElementBase objects."""
|
||||
|
||||
class TestStanza(ElementBase):
|
||||
name = "foo"
|
||||
namespace = "foo"
|
||||
interfaces = set(('bar', 'baz'))
|
||||
|
||||
stanza1 = TestStanza()
|
||||
stanza1['bar'] = 'a'
|
||||
|
||||
self.failUnless(stanza1,
|
||||
"Stanza object does not evaluate to True")
|
||||
|
||||
stanza2 = TestStanza()
|
||||
stanza2['baz'] = 'b'
|
||||
|
||||
self.failUnless(stanza1 != stanza2,
|
||||
"Different stanza objects incorrectly compared equal.")
|
||||
|
||||
stanza1['baz'] = 'b'
|
||||
stanza2['bar'] = 'a'
|
||||
|
||||
self.failUnless(stanza1 == stanza2,
|
||||
"Equal stanzas incorrectly compared inequal.")
|
||||
|
||||
def testKeys(self):
|
||||
"""Test extracting interface names from a stanza object."""
|
||||
|
||||
class TestStanza(ElementBase):
|
||||
name = "foo"
|
||||
namespace = "foo"
|
||||
interfaces = set(('bar', 'baz'))
|
||||
plugin_attrib = 'qux'
|
||||
|
||||
registerStanzaPlugin(TestStanza, TestStanza)
|
||||
|
||||
stanza = TestStanza()
|
||||
|
||||
self.failUnless(set(stanza.keys()) == set(('bar', 'baz')),
|
||||
"Returned set of interface keys does not match expected.")
|
||||
|
||||
stanza.enable('qux')
|
||||
|
||||
self.failUnless(set(stanza.keys()) == set(('bar', 'baz', 'qux')),
|
||||
"Incorrect set of interface and plugin keys.")
|
||||
|
||||
def testGet(self):
|
||||
"""Test accessing stanza interfaces using get()."""
|
||||
|
||||
class TestStanza(ElementBase):
|
||||
name = "foo"
|
||||
namespace = "foo"
|
||||
interfaces = set(('bar', 'baz'))
|
||||
|
||||
stanza = TestStanza()
|
||||
stanza['bar'] = 'a'
|
||||
|
||||
self.failUnless(stanza.get('bar') == 'a',
|
||||
"Incorrect value returned by stanza.get")
|
||||
|
||||
self.failUnless(stanza.get('baz', 'b') == 'b',
|
||||
"Incorrect default value returned by stanza.get")
|
||||
|
||||
def testSubStanzas(self):
|
||||
"""Test manipulating substanzas of a stanza object."""
|
||||
|
||||
class TestSubStanza(ElementBase):
|
||||
name = "foobar"
|
||||
namespace = "foo"
|
||||
interfaces = set(('qux',))
|
||||
|
||||
class TestStanza(ElementBase):
|
||||
name = "foo"
|
||||
namespace = "foo"
|
||||
interfaces = set(('bar', 'baz'))
|
||||
subitem = (TestSubStanza,)
|
||||
|
||||
stanza = TestStanza()
|
||||
substanza1 = TestSubStanza()
|
||||
substanza2 = TestSubStanza()
|
||||
substanza1['qux'] = 'a'
|
||||
substanza2['qux'] = 'b'
|
||||
|
||||
# Test appending substanzas
|
||||
self.failUnless(len(stanza) == 0,
|
||||
"Incorrect empty stanza size.")
|
||||
|
||||
stanza.append(substanza1)
|
||||
self.checkStanza(TestStanza, stanza, """
|
||||
<foo xmlns="foo">
|
||||
<foobar qux="a" />
|
||||
</foo>
|
||||
""")
|
||||
self.failUnless(len(stanza) == 1,
|
||||
"Incorrect stanza size with 1 substanza.")
|
||||
|
||||
stanza.append(substanza2)
|
||||
self.checkStanza(TestStanza, stanza, """
|
||||
<foo xmlns="foo">
|
||||
<foobar qux="a" />
|
||||
<foobar qux="b" />
|
||||
</foo>
|
||||
""")
|
||||
self.failUnless(len(stanza) == 2,
|
||||
"Incorrect stanza size with 2 substanzas.")
|
||||
|
||||
# Test popping substanzas
|
||||
stanza.pop(0)
|
||||
self.checkStanza(TestStanza, stanza, """
|
||||
<foo xmlns="foo">
|
||||
<foobar qux="b" />
|
||||
</foo>
|
||||
""")
|
||||
|
||||
# Test iterating over substanzas
|
||||
stanza.append(substanza1)
|
||||
results = []
|
||||
for substanza in stanza:
|
||||
results.append(substanza['qux'])
|
||||
self.failUnless(results == ['b', 'a'],
|
||||
"Iteration over substanzas failed: %s." % str(results))
|
||||
|
||||
def testCopy(self):
|
||||
"""Test copying stanza objects."""
|
||||
|
||||
class TestStanza(ElementBase):
|
||||
name = "foo"
|
||||
namespace = "foo"
|
||||
interfaces = set(('bar', 'baz'))
|
||||
|
||||
stanza1 = TestStanza()
|
||||
stanza1['bar'] = 'a'
|
||||
|
||||
stanza2 = stanza1.__copy__()
|
||||
|
||||
self.failUnless(stanza1 == stanza2,
|
||||
"Copied stanzas are not equal to each other.")
|
||||
|
||||
stanza1['baz'] = 'b'
|
||||
self.failUnless(stanza1 != stanza2,
|
||||
"Divergent stanza copies incorrectly compared equal.")
|
||||
|
||||
suite = unittest.TestLoader().loadTestsFromTestCase(TestElementBase)
|
||||
|
|
Loading…
Reference in a new issue