aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorOlivier Tilloy <olivier@tilloy.net>2009-04-28 09:57:39 +0200
committerOlivier Tilloy <olivier@tilloy.net>2009-04-28 09:57:39 +0200
commit2ef1f72a9b8f7d6ad82ee4afd57612c1f0fdbb0e (patch)
tree55bcd886df0aef187c795a341503f60f778ff5e4
parentf629259e03df06bc710d643a846aa9d03b0c0af2 (diff)
downloadpyexiv2-2ef1f72a9b8f7d6ad82ee4afd57612c1f0fdbb0e.tar.gz
First incomplete draft of a notifying list object.
-rw-r--r--src/pyexiv2.py64
-rwxr-xr-xunittest/TestsRunner.py2
-rw-r--r--unittest/notifying_list.py88
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, ...)