Allow a stanza plugin to override a parent's interfaces.

Each interface, say foo, may be overridden in three ways:
    set_foo
    get_foo
    del_foo

To declare an override in a plugin, add the class field
overrides as so:
    overrides = ['set_foo', 'del_foo']

Each override must have a matching set_foo(), etc method
for implementing the new behaviour.

To enable the overrides for a particular parent stanza,
pass the option overrides=True to register_stanza_plugin.

    register_stanza_plugin(Stanza, Plugin, overrides=True)

Example code:

class Test(ElementBase):

    name = 'test'
    namespace = 'testing'
    interfaces = set(('foo', 'bar'))
    sub_interfaces = set(('bar',))

class TestOverride(ElementBase):

    name = 'test-override'
    namespace = 'testing'
    plugin_attrib = 'override'
    interfaces = set(('foo',))
    overrides = ['set_foo']

    def setup(self, xml):
        # Don't include an XML element in the parent stanza
        # since we're adding just an attribute.
        # If adding a regular subelement, no need to do this.
        self.xml = ET.Element('')

    def set_foo(self, value):
        print("overrides!")
        self.parent()._set_attr('foo', 'override-%s' % value)

register_stanza_plugin(Test, TestOverride, overrides=True)

Example usage:
>>> t = TestStanza()
>>> t['foo'] = 'bar'
>>> t['foo']
'override-bar'
This commit is contained in:
Lance Stout 2011-03-24 12:25:17 -04:00
parent 84e2589f22
commit 6d45971411
2 changed files with 167 additions and 32 deletions

View File

@ -24,24 +24,32 @@ log = logging.getLogger(__name__)
XML_TYPE = type(ET.Element('xml')) XML_TYPE = type(ET.Element('xml'))
def register_stanza_plugin(stanza, plugin, iterable=False): def register_stanza_plugin(stanza, plugin, iterable=False, overrides=False):
""" """
Associate a stanza object as a plugin for another stanza. Associate a stanza object as a plugin for another stanza.
Arguments: Arguments:
stanza -- The class of the parent stanza. stanza -- The class of the parent stanza.
plugin -- The class of the plugin stanza. plugin -- The class of the plugin stanza.
iterable -- Indicates if the plugin stanza iterable -- Indicates if the plugin stanza should be
should be included in the parent included in the parent stanza's iterable
stanza's iterable 'substanzas' 'substanzas' interface results.
interface results. overrides -- Indicates if the plugin should be allowed
to override the interface handlers for
the parent stanza.
""" """
tag = "{%s}%s" % (plugin.namespace, plugin.name) tag = "{%s}%s" % (plugin.namespace, plugin.name)
stanza.plugin_attrib_map[plugin.plugin_attrib] = plugin stanza.plugin_attrib_map[plugin.plugin_attrib] = plugin
stanza.plugin_tag_map[tag] = plugin stanza.plugin_tag_map[tag] = plugin
if iterable: if iterable:
# Prevent weird memory reference gotchas.
stanza.plugin_iterables = stanza.plugin_iterables.copy() stanza.plugin_iterables = stanza.plugin_iterables.copy()
stanza.plugin_iterables.add(plugin) stanza.plugin_iterables.add(plugin)
if overrides:
# Prevent weird memory reference gotchas.
stanza.plugin_overrides = stanza.plugin_overrides.copy()
for interface in plugin.overrides:
stanza.plugin_overrides[interface] = plugin.plugin_attrib
# To maintain backwards compatibility for now, preserve the camel case name. # To maintain backwards compatibility for now, preserve the camel case name.
@ -130,6 +138,11 @@ class ElementBase(object):
subitem -- A set of stanza classes which are allowed to subitem -- A set of stanza classes which are allowed to
be added as substanzas. Deprecated version be added as substanzas. Deprecated version
of plugin_iterables. of plugin_iterables.
overrides -- A list of interfaces prepended with 'get_',
'set_', or 'del_'. If the stanza is registered
as a plugin with overrides=True, then the
parent's interface handlers will be
overridden by the plugin's matching handler.
types -- A set of generic type attribute values. types -- A set of generic type attribute values.
tag -- The namespaced name of the stanza's root tag -- The namespaced name of the stanza's root
element. Example: "{foo_ns}bar" element. Example: "{foo_ns}bar"
@ -139,6 +152,10 @@ class ElementBase(object):
associated plugin stanza classes. associated plugin stanza classes.
plugin_iterables -- A set of stanza classes which are allowed to plugin_iterables -- A set of stanza classes which are allowed to
be added as substanzas. be added as substanzas.
plugin_overrides -- A mapping of interfaces prepended with 'get_',
'set_' or 'del_' to plugin attrib names. Allows
a plugin to override the behaviour of a parent
stanza's interface handlers.
plugin_tag_map -- A mapping of plugin stanza tag names with plugin_tag_map -- A mapping of plugin stanza tag names with
the associated plugin stanza classes. the associated plugin stanza classes.
is_extension -- When True, allows the stanza to provide one is_extension -- When True, allows the stanza to provide one
@ -204,7 +221,9 @@ class ElementBase(object):
interfaces = set(('type', 'to', 'from', 'id', 'payload')) interfaces = set(('type', 'to', 'from', 'id', 'payload'))
types = set(('get', 'set', 'error', None, 'unavailable', 'normal', 'chat')) types = set(('get', 'set', 'error', None, 'unavailable', 'normal', 'chat'))
sub_interfaces = tuple() sub_interfaces = tuple()
overrides = {}
plugin_attrib_map = {} plugin_attrib_map = {}
plugin_overrides = {}
plugin_iterables = set() plugin_iterables = set()
plugin_tag_map = {} plugin_tag_map = {}
subitem = set() subitem = set()
@ -380,12 +399,13 @@ class ElementBase(object):
The search order for interface value retrieval for an interface The search order for interface value retrieval for an interface
named 'foo' is: named 'foo' is:
1. The list of substanzas. 1. The list of substanzas.
2. The result of calling get_foo. 2. The result of calling the get_foo override handler.
3. The result of calling getFoo. 3. The result of calling get_foo.
4. The contents of the foo subelement, if foo is a sub interface. 4. The result of calling getFoo.
5. The value of the foo attribute of the XML object. 5. The contents of the foo subelement, if foo is a sub interface.
6. The plugin named 'foo' 6. The value of the foo attribute of the XML object.
7. An empty string. 7. The plugin named 'foo'
8. An empty string.
Arguments: Arguments:
attrib -- The name of the requested stanza interface. attrib -- The name of the requested stanza interface.
@ -395,6 +415,16 @@ class ElementBase(object):
elif attrib in self.interfaces: elif attrib in self.interfaces:
get_method = "get_%s" % attrib.lower() get_method = "get_%s" % attrib.lower()
get_method2 = "get%s" % attrib.title() get_method2 = "get%s" % attrib.title()
if self.plugin_overrides:
plugin = self.plugin_overrides.get(get_method, None)
if plugin:
if plugin not in self.plugins:
self.init_plugin(plugin)
handler = getattr(self.plugins[plugin], get_method, None)
if handler:
return handler()
if hasattr(self, get_method): if hasattr(self, get_method):
return getattr(self, get_method)() return getattr(self, get_method)()
elif hasattr(self, get_method2): elif hasattr(self, get_method2):
@ -429,13 +459,14 @@ class ElementBase(object):
The effect of interface value assignment for an interface The effect of interface value assignment for an interface
named 'foo' will be one of: named 'foo' will be one of:
1. Delete the interface's contents if the value is None. 1. Delete the interface's contents if the value is None.
2. Call set_foo, if it exists. 2. Call the set_foo override handler, if it exists.
3. Call setFoo, if it exists. 3. Call set_foo, if it exists.
4. Set the text of a foo element, if foo is in sub_interfaces. 4. Call setFoo, if it exists.
5. Set the value of a top level XML attribute name foo. 5. Set the text of a foo element, if foo is in sub_interfaces.
6. Attempt to pass value to a plugin named foo using the plugin's 6. Set the value of a top level XML attribute name foo.
7. Attempt to pass value to a plugin named foo using the plugin's
foo interface. foo interface.
7. Do nothing. 8. Do nothing.
Arguments: Arguments:
attrib -- The name of the stanza interface to modify. attrib -- The name of the stanza interface to modify.
@ -445,6 +476,16 @@ class ElementBase(object):
if value is not None: if value is not None:
set_method = "set_%s" % attrib.lower() set_method = "set_%s" % attrib.lower()
set_method2 = "set%s" % attrib.title() set_method2 = "set%s" % attrib.title()
if self.plugin_overrides:
plugin = self.plugin_overrides.get(set_method, None)
if plugin:
if plugin not in self.plugins:
self.init_plugin(plugin)
handler = getattr(self.plugins[plugin], set_method, None)
if handler:
return handler(value)
if hasattr(self, set_method): if hasattr(self, set_method):
getattr(self, set_method)(value,) getattr(self, set_method)(value,)
elif hasattr(self, set_method2): elif hasattr(self, set_method2):
@ -480,12 +521,13 @@ class ElementBase(object):
The effect of deleting a stanza interface value named foo will be The effect of deleting a stanza interface value named foo will be
one of: one of:
1. Call del_foo, if it exists. 1. Call del_foo override handler, if it exists.
2. Call delFoo, if it exists. 2. Call del_foo, if it exists.
3. Delete foo element, if foo is in sub_interfaces. 3. Call delFoo, if it exists.
4. Delete top level XML attribute named foo. 4. Delete foo element, if foo is in sub_interfaces.
5. Remove the foo plugin, if it was loaded. 5. Delete top level XML attribute named foo.
6. Do nothing. 6. Remove the foo plugin, if it was loaded.
7. Do nothing.
Arguments: Arguments:
attrib -- The name of the affected stanza interface. attrib -- The name of the affected stanza interface.
@ -493,6 +535,16 @@ class ElementBase(object):
if attrib in self.interfaces: if attrib in self.interfaces:
del_method = "del_%s" % attrib.lower() del_method = "del_%s" % attrib.lower()
del_method2 = "del%s" % attrib.title() del_method2 = "del%s" % attrib.title()
if self.plugin_overrides:
plugin = self.plugin_overrides.get(del_method, None)
if plugin:
if plugin not in self.plugins:
self.init_plugin(plugin)
handler = getattr(self.plugins[plugin], del_method, None)
if handler:
return handler()
if hasattr(self, del_method): if hasattr(self, del_method):
getattr(self, del_method)() getattr(self, del_method)()
elif hasattr(self, del_method2): elif hasattr(self, del_method2):

View File

@ -53,9 +53,8 @@ class TestElementBase(SleekTest):
name = "foo" name = "foo"
namespace = "foo" namespace = "foo"
interfaces = set(('bar', 'baz')) interfaces = set(('bar', 'baz'))
subitem = set((TestSubStanza,))
register_stanza_plugin(TestStanza, TestStanzaPlugin) register_stanza_plugin(TestStanza, TestStanzaPlugin, iterable=True)
stanza = TestStanza() stanza = TestStanza()
stanza['bar'] = 'a' stanza['bar'] = 'a'
@ -100,8 +99,8 @@ class TestElementBase(SleekTest):
name = "foo" name = "foo"
namespace = "foo" namespace = "foo"
interfaces = set(('bar', 'baz')) interfaces = set(('bar', 'baz'))
subitem = set((TestSubStanza,))
register_stanza_plugin(TestStanza, TestSubStanza, iterable=True)
register_stanza_plugin(TestStanza, TestStanzaPlugin) register_stanza_plugin(TestStanza, TestStanzaPlugin)
register_stanza_plugin(TestStanza, TestStanzaPlugin2) register_stanza_plugin(TestStanza, TestStanzaPlugin2)
@ -115,7 +114,7 @@ class TestElementBase(SleekTest):
'substanzas': [{'__childtag__': '{foo}subfoo', 'substanzas': [{'__childtag__': '{foo}subfoo',
'bar': 'c', 'bar': 'c',
'baz': ''}]} 'baz': ''}]}
stanza.setStanzaValues(values) stanza.values = values
self.check(stanza, """ self.check(stanza, """
<foo xmlns="foo" bar="a"> <foo xmlns="foo" bar="a">
@ -143,7 +142,7 @@ class TestElementBase(SleekTest):
plugin_attrib = "foobar" plugin_attrib = "foobar"
interfaces = set(('fizz',)) interfaces = set(('fizz',))
TestStanza.subitem = (TestStanza,) register_stanza_plugin(TestStanza, TestStanza, iterable=True)
register_stanza_plugin(TestStanza, TestStanzaPlugin) register_stanza_plugin(TestStanza, TestStanzaPlugin)
stanza = TestStanza() stanza = TestStanza()
@ -457,7 +456,6 @@ class TestElementBase(SleekTest):
namespace = "foo" namespace = "foo"
interfaces = set(('bar','baz', 'qux')) interfaces = set(('bar','baz', 'qux'))
sub_interfaces = set(('qux',)) sub_interfaces = set(('qux',))
subitem = (TestSubStanza,)
def setQux(self, value): def setQux(self, value):
self._set_sub_text('qux', text=value) self._set_sub_text('qux', text=value)
@ -470,6 +468,7 @@ class TestElementBase(SleekTest):
namespace = "http://test/slash/bar" namespace = "http://test/slash/bar"
interfaces = set(('attrib',)) interfaces = set(('attrib',))
register_stanza_plugin(TestStanza, TestSubStanza, iterable=True)
register_stanza_plugin(TestStanza, TestStanzaPlugin) register_stanza_plugin(TestStanza, TestStanzaPlugin)
stanza = TestStanza() stanza = TestStanza()
@ -590,7 +589,8 @@ class TestElementBase(SleekTest):
name = "foo" name = "foo"
namespace = "foo" namespace = "foo"
interfaces = set(('bar', 'baz')) interfaces = set(('bar', 'baz'))
subitem = (TestSubStanza,)
register_stanza_plugin(TestStanza, TestSubStanza, iterable=True)
stanza = TestStanza() stanza = TestStanza()
substanza1 = TestSubStanza() substanza1 = TestSubStanza()
@ -657,4 +657,87 @@ class TestElementBase(SleekTest):
self.failUnless(stanza1 != stanza2, self.failUnless(stanza1 != stanza2,
"Divergent stanza copies incorrectly compared equal.") "Divergent stanza copies incorrectly compared equal.")
def testExtension(self):
"""Testing using is_extension."""
class TestStanza(ElementBase):
name = "foo"
namespace = "foo"
interfaces = set(('bar', 'baz'))
class TestExtension(ElementBase):
name = 'extended'
namespace = 'foo'
plugin_attrib = name
interfaces = set((name,))
is_extension = True
def set_extended(self, value):
self.xml.text = value
def get_extended(self):
return self.xml.text
def del_extended(self):
self.parent().xml.remove(self.xml)
register_stanza_plugin(TestStanza, TestExtension)
stanza = TestStanza()
stanza['extended'] = 'testing'
self.check(stanza, """
<foo xmlns="foo">
<extended>testing</extended>
</foo>
""")
self.failUnless(stanza['extended'] == 'testing',
"Could not retrieve stanza extension value.")
del stanza['extended']
self.check(stanza, """
<foo xmlns="foo" />
""")
def testOverrides(self):
"""Test using interface overrides."""
class TestStanza(ElementBase):
name = "foo"
namespace = "foo"
interfaces = set(('bar', 'baz'))
class TestOverride(ElementBase):
name = 'overrider'
namespace = 'foo'
plugin_attrib = name
interfaces = set(('bar',))
overrides = ['set_bar']
def setup(self, xml):
# Don't create XML for the plugin
self.xml = ET.Element('')
def set_bar(self, value):
if not value.startswith('override-'):
self.parent()._set_attr('bar', 'override-%s' % value)
else:
self.parent()._set_attr('bar', value)
stanza = TestStanza()
stanza['bar'] = 'foo'
self.check(stanza, """
<foo xmlns="foo" bar="foo" />
""")
register_stanza_plugin(TestStanza, TestOverride, overrides=True)
stanza = TestStanza()
stanza['bar'] = 'foo'
self.check(stanza, """
<foo xmlns="foo" bar="override-foo" />
""")
suite = unittest.TestLoader().loadTestsFromTestCase(TestElementBase) suite = unittest.TestLoader().loadTestsFromTestCase(TestElementBase)