diff options
author | Olivier Tilloy <olivier@tilloy.net> | 2009-04-28 09:57:39 +0200 |
---|---|---|
committer | Olivier Tilloy <olivier@tilloy.net> | 2009-04-28 09:57:39 +0200 |
commit | 2ef1f72a9b8f7d6ad82ee4afd57612c1f0fdbb0e (patch) | |
tree | 55bcd886df0aef187c795a341503f60f778ff5e4 | |
parent | f629259e03df06bc710d643a846aa9d03b0c0af2 (diff) | |
download | pyexiv2-2ef1f72a9b8f7d6ad82ee4afd57612c1f0fdbb0e.tar.gz |
First incomplete draft of a notifying list object.
-rw-r--r-- | src/pyexiv2.py | 64 | ||||
-rwxr-xr-x | unittest/TestsRunner.py | 2 | ||||
-rw-r--r-- | unittest/notifying_list.py | 88 |
3 files changed, 151 insertions, 3 deletions
diff --git a/src/pyexiv2.py b/src/pyexiv2.py index 309c112..87a33c9 100644 --- a/src/pyexiv2.py +++ b/src/pyexiv2.py @@ -246,6 +246,54 @@ class Rational(object): return '%d/%d' % (self.numerator, self.denominator) +class ListenerInterface(object): + # Define an interface that an object that wants to listen to changes on a + # notifying list should implement. + + def item_changed(self, index, item): + raise NotImplementedError() + + def item_deleted(self, index): + raise NotImplementedError() + + # TODO: define other methods. + + +class NotifyingList(list): + + """ + DOCME + Not asynchronous. + """ + + # file:///usr/share/doc/python2.5/html/lib/typesseq-mutable.html + # http://docs.python.org/reference/datamodel.html#additional-methods-for-emulation-of-sequence-types + + def __init__(self, items=[]): + super(NotifyingList, self).__init__(items) + self._listeners = set() + + def register_listener(self, listener): + self._listeners.add(listener) + + def unregister_listener(self, listener): + self._listeners.remove(listener) + + def _notify_listeners(self, method_name, *args): + for listener in self._listeners: + getattr(listener, method_name)(*args) + + def __setitem__(self, index, item): + # FIXME: support slice arguments + super(NotifyingList, self).__setitem__(index, item) + self._notify_listeners('item_changed', index, item) + + def __delitem__(self, index): + # FIXME: support slice arguments + super(NotifyingList, self).__delitem__(index) + self._notify_listeners('item_deleted', index) + + class MetadataTag(object): """ @@ -516,7 +564,7 @@ class IptcValueError(ValueError): (self.xtype, self.value) -class IptcTag(MetadataTag): +class IptcTag(MetadataTag, ListenerInterface): """ An IPTC metadata tag can have several values (tags that have the repeatable @@ -533,8 +581,8 @@ class IptcTag(MetadataTag): Constructor. """ super(IptcTag, self).__init__(key, name, label, description, xtype, values) - # FIXME: make values either a tuple (immutable) or a notifying list - self._values = map(lambda x: IptcTag._convert_to_python(x, xtype), values) + self._values = NotifyingList(map(lambda x: IptcTag._convert_to_python(x, xtype), values)) + self._values.register_listener(self) def _get_values(self): return self._values @@ -548,12 +596,22 @@ class IptcTag(MetadataTag): def _del_values(self): if self.metadata is not None: self.metadata._delete_iptc_tag(self.key) + self._values.unregister_listener(self) del self._values # DOCME values = property(fget=_get_values, fset=_set_values, fdel=_del_values, doc=None) + # Implement the listener interface. + + def item_changed(self, index, item): + # An item of self._values was changed. + # The following is a quick, non optimal solution. + # FIXME: do not update the whole list, only the item that changed. + self._set_values(self._values) + + @staticmethod def _convert_to_python(value, xtype): """ diff --git a/unittest/TestsRunner.py b/unittest/TestsRunner.py index d4f52dc..6021453 100755 --- a/unittest/TestsRunner.py +++ b/unittest/TestsRunner.py @@ -35,6 +35,7 @@ import unittest #from Bug183332_TestCase import Bug183332_TestCase #from Bug183618_TestCase import Bug183618_TestCase from rational import TestRational +from notifying_list import TestNotifyingList from exif import TestExifTag from iptc import TestIptcTag from xmp import TestXmpTag @@ -51,6 +52,7 @@ if __name__ == '__main__': #suite.addTest(unittest.defaultTestLoader.loadTestsFromTestCase(Bug183332_TestCase)) #suite.addTest(unittest.defaultTestLoader.loadTestsFromTestCase(Bug183618_TestCase)) suite.addTest(unittest.defaultTestLoader.loadTestsFromTestCase(TestRational)) + suite.addTest(unittest.defaultTestLoader.loadTestsFromTestCase(TestNotifyingList)) suite.addTest(unittest.defaultTestLoader.loadTestsFromTestCase(TestExifTag)) suite.addTest(unittest.defaultTestLoader.loadTestsFromTestCase(TestIptcTag)) suite.addTest(unittest.defaultTestLoader.loadTestsFromTestCase(TestXmpTag)) diff --git a/unittest/notifying_list.py b/unittest/notifying_list.py new file mode 100644 index 0000000..05372be --- /dev/null +++ b/unittest/notifying_list.py @@ -0,0 +1,88 @@ +# -*- coding: utf-8 -*- + +# ****************************************************************************** +# +# Copyright (C) 2009 Olivier Tilloy <olivier@tilloy.net> +# +# This file is part of the pyexiv2 distribution. +# +# pyexiv2 is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# pyexiv2 is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with pyexiv2; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, 5th Floor, Boston, MA 02110-1301 USA. +# +# Author: Olivier Tilloy <olivier@tilloy.net> +# +# ****************************************************************************** + +import unittest +from pyexiv2 import ListenerInterface, NotifyingList +import random + + +class SimpleListener(ListenerInterface): + + # Total number of notifications + notifications = 0 + # Last notification: (method_name, *args) + last = None + + def item_changed(self, index, item): + self.notifications += 1 + self.last = ('item_changed', index, item) + + def item_deleted(self, index): + self.notifications += 1 + self.last = ('item_deleted', index) + + +class TestNotifyingList(unittest.TestCase): + + def setUp(self): + self.values = NotifyingList([5, 7, 9, 14, 57, 3, 2]) + + def test_no_listener(self): + # No listener is registered, nothing should happen. + self.values[3] = 13 + # TODO: test all operations (insertion, deletion, slicing, ...) + + def test_listener_interface(self): + self.values.register_listener(ListenerInterface()) + self.failUnlessRaises(NotImplementedError, self.values.__setitem__, 3, 13) + self.failUnlessRaises(NotImplementedError, self.values.__delitem__, 5) + # TODO: test all operations (insertion, slicing, ...) + + def test_one_listener(self): + listener = SimpleListener() + self.values.register_listener(listener) + self.values[3] = 13 + self.failUnlessEqual(listener.notifications, 1) + self.failUnlessEqual(listener.last, ('item_changed', 3, 13)) + del self.values[5] + self.failUnlessEqual(listener.notifications, 2) + self.failUnlessEqual(listener.last, ('item_deleted', 5)) + # TODO: test all operations (insertion, slicing, ...) + + def test_multiple_listeners(self): + # Register a random number of listeners + listeners = [SimpleListener() for i in xrange(random.randint(3, 20))] + for listener in listeners: + self.values.register_listener(listener) + self.values[3] = 13 + for listener in listeners: + self.failUnlessEqual(listener.notifications, 1) + self.failUnlessEqual(listener.last, ('item_changed', 3, 13)) + del self.values[5] + for listener in listeners: + self.failUnlessEqual(listener.notifications, 2) + self.failUnlessEqual(listener.last, ('item_deleted', 5)) + # TODO: test all operations (insertion, slicing, ...) |