Commits

Vinay Sajip  committed 48b4805

Added events.

  • Participants
  • Parent commits dea7a2f

Comments (0)

Files changed (3)

File distlib/util.py

 # Copyright (C) 2012 The Python Software Foundation.
 # See LICENSE.txt and CONTRIBUTORS.txt.
 #
+from collections import deque
 import contextlib
 import json
 import logging
     result = _get_external_data(url)
     #logger.debug('get_release_data done: %s', name)
     return result
+
+class EventMixin(object):
+    """
+    A very simple publish/subscribe system.
+    """
+    def __init__(self):
+        self._subscribers = {}
+
+    def add(self, event, subscriber, append=True):
+        """
+        Add a subscriber for an event.
+
+        :param event: The name of an event.
+        :param subscriber: The subscriber to be added (and called when the
+                           event is published).
+        :param append: Whether to append or prepend the subscriber to an
+                       existing subscriber list for the event.
+        """
+        subs = self._subscribers
+        if event not in subs:
+            subs[event] = deque([subscriber])
+        else:
+            sq = subs[event]
+            if append:
+                sq.append(subscriber)
+            else:
+                sq.appendleft(subscriber)
+
+    def remove(self, event, subscriber):
+        """
+        Remove a subscriber for an event.
+
+        :param event: The name of an event.
+        :param subscriber: The subscriber to be removed.
+        """
+        subs = self._subscribers
+        if event not in subs:
+            raise ValueError('No subscribers: %r' % event)
+        subs[event].remove(subscriber)
+
+    def get_subscribers(self, event):
+        """
+        Return an iterator for the subscribers for an event.
+        :param event: The event to return subscribers for.
+        """
+        return iter(self._subscribers.get(event, ()))
+
+    def publish(self, event, *args, **kwargs):
+        """
+        Publish a event and return a list of values returned by its
+        subscribers.
+
+        :param event: The event to publish.
+        :param args: The positional arguments to pass to the event's
+                     subscribers.
+        :param kwargs: The keyword arguments to pass to the event's
+                       subscribers.
+        """
+        result = []
+        for subscriber in self.get_subscribers(event):
+            try:
+                value = subscriber(event, *args, **kwargs)
+            except Exception:
+                logger.exception('Exception during event publication')
+                value = None
+            result.append(value)
+        logger.debug('%s: args = %s, kwargs = %s, result = %s',
+                     event, args, kwargs, result)
+        return result

File tests/test_locators.py

 HERE = os.path.abspath(os.path.dirname(__file__))
 
 class LocatorTestCase(unittest.TestCase):
+
+    @unittest.skipIf('SKIP_SLOW' in os.environ, 'Skipping slow test')
     def test_xmlrpc(self):
         locator = PyPIRPCLocator('http://python.org/pypi')
         result = locator.get_project('sarge')
                          '961ddd9bc085fdd8b248c6dd96ceb1c8')
         self.assertRaises(NotImplementedError, locator.get_distribution_names)
 
+    @unittest.skipIf('SKIP_SLOW' in os.environ, 'Skipping slow test')
     def test_scraper(self):
         locator = SimpleScrapingLocator('http://pypi.python.org/simple/')
         for name in ('sarge', 'Sarge'):
         finally:
             sys.path.pop(0)
 
+    @unittest.skipIf('SKIP_SLOW' in os.environ, 'Skipping slow test')
     def test_aggregation(self):
         d = os.path.join(HERE, 'fake_archives')
         loc1 = DirectoryLocator(d)

File tests/test_util.py

+from itertools import islice
 import os
 
 from compat import unittest
 from distlib import DistlibException
 from distlib.util import (get_export_entry, ExportEntry, resolve,
                           get_cache_base, path_to_cache_dir,
-                          parse_credentials, ensure_slash, split_filename)
+                          parse_credentials, ensure_slash, split_filename,
+                          EventMixin)
 
 class UtilTestCase(unittest.TestCase):
     def check_entry(self, entry, name, prefix, suffix, flags):
         #import pdb; pdb.set_trace()
         #self.assertEqual(split_filename('asv_files-test-dev-20120501-01', 'asv_files'),
         #                 ('asv_files-test', 'dev-20120501-01', None))
+
+    def test_events(self):
+        collected = []
+
+        def handler1(e, *args, **kwargs):
+            collected.append((1, e, args, kwargs))
+
+        def handler2(e, *args, **kwargs):
+            collected.append((2, e, args, kwargs))
+
+        def handler3(e, *args, **kwargs):
+            if not args:
+                raise NotImplementedError('surprise!')
+            collected.append((3, e, args, kwargs))
+            return (args, kwargs)
+
+        e = EventMixin()
+        e.add('A', handler1)
+        self.assertRaises(ValueError, e.remove, 'B', handler1)
+
+        cases = (
+            ((1, 2), {'buckle': 'my shoe'}),
+            ((3, 4), {'shut': 'the door'}),
+        )
+
+        for case in cases:
+            e.publish('A', *case[0], **case[1])
+            e.publish('B', *case[0], **case[1])
+
+        for actual, source in zip(collected, cases):
+            self.assertEqual(actual, (1, 'A') + source[:1] + source[1:])
+
+        collected = []
+        e.add('B', handler2)
+
+        self.assertEqual(tuple(e.get_subscribers('A')), (handler1,))
+        self.assertEqual(tuple(e.get_subscribers('B')), (handler2,))
+        self.assertEqual(tuple(e.get_subscribers('C')), ())
+
+        for case in cases:
+            e.publish('A', *case[0], **case[1])
+            e.publish('B', *case[0], **case[1])
+
+        actuals = islice(collected, 0, None, 2)
+        for actual, source in zip(actuals, cases):
+            self.assertEqual(actual, (1, 'A') + source[:1] + source[1:])
+
+        actuals = islice(collected, 1, None, 2)
+        for actual, source in zip(actuals, cases):
+            self.assertEqual(actual, (2, 'B') + source[:1] + source[1:])
+
+        e.remove('B', handler2)
+
+        collected = []
+
+        for case in cases:
+            e.publish('A', *case[0], **case[1])
+            e.publish('B', *case[0], **case[1])
+
+        for actual, source in zip(collected, cases):
+            self.assertEqual(actual, (1, 'A') + source[:1] + source[1:])
+
+        e.add('C', handler3)
+
+        collected = []
+        returned = []
+
+        for case in cases:
+            returned.extend(e.publish('C', *case[0], **case[1]))
+            returned.extend(e.publish('C'))
+
+        for actual, source in zip(collected, cases):
+            self.assertEqual(actual, (3, 'C') + source[:1] + source[1:])
+
+        self.assertEqual(tuple(islice(returned, 1, None, 2)), (None, None))
+        actuals = islice(returned, 0, None, 2)
+        for actual, expected in zip(actuals, cases):
+            self.assertEqual(actual, expected)