← Back to team overview

launchpad-reviewers team mailing list archive

[Merge] lp:~allenap/launchpad/longpoll-storm-events-subscribe into lp:launchpad

 

Gavin Panella has proposed merging lp:~allenap/launchpad/longpoll-storm-events-subscribe into lp:launchpad with lp:~allenap/launchpad/longpoll-storm-events as a prerequisite.

Requested reviews:
  Launchpad code reviewers (launchpad-reviewers)

For more details, see:
https://code.launchpad.net/~allenap/launchpad/longpoll-storm-events-subscribe/+merge/76363

There are a few changes in here:

- For ILongPollEvent, switch from multi-adapters to single-adapters,
  optionally named. This means that, for most objects, there can be a
  default event registered, and more esoteric things can be registered
  by name.

  The Storm -> ILongPollEvent adapter takes advantage of this;
  interested code can subscribe to any Storm object in a single step
  and receive all lifecycle events about that object (that the object
  chooses to publish via zope.event.notify).

- Change ILongPollEvent.emit() to accept keyword arguments instead of
  a blob. I was concerned that there was just too much nesting of data
  structures going on, it was getting confusing. Now the keyword
  arguments are merged with an event_key item to form the message
  payload.

- Rename LongPollSubscriber to LongPollApplicationRequestSubscriber
  because that's what it is; it's not intended as a reusable
  base-class.

-- 
https://code.launchpad.net/~allenap/launchpad/longpoll-storm-events-subscribe/+merge/76363
Your team Launchpad code reviewers is requested to review the proposed merge of lp:~allenap/launchpad/longpoll-storm-events-subscribe into lp:launchpad.
=== modified file 'lib/lp/app/longpoll/__init__.py'
--- lib/lp/app/longpoll/__init__.py	2011-09-16 15:01:47 +0000
+++ lib/lp/app/longpoll/__init__.py	2011-09-21 09:20:47 +0000
@@ -10,7 +10,7 @@
     ]
 
 from lazr.restful.utils import get_current_browser_request
-from zope.component import getMultiAdapter
+from zope.component import getAdapter
 
 from lp.services.longpoll.interfaces import (
     ILongPollEvent,
@@ -18,16 +18,17 @@
     )
 
 
-def subscribe(target, event, request=None):
+def subscribe(target, event_name=u"", request=None):
     """Convenience method to subscribe the current request.
 
     :param target: Something that can be adapted to `ILongPollEvent`.
-    :param event: The name of the event to subscribe to.
+    :param event_name: The name of the event to subscribe to. This is used to
+        look up a named adapter from `target` to `ILongPollEvent`.
     :param request: The request for which to get an `ILongPollSubscriber`. It
         a request is not specified the currently active request is used.
     :return: The `ILongPollEvent` that has been subscribed to.
     """
-    event = getMultiAdapter((target, event), ILongPollEvent)
+    event = getAdapter(target, ILongPollEvent, name=event_name)
     if request is None:
         request = get_current_browser_request()
     subscriber = ILongPollSubscriber(request)
@@ -35,14 +36,15 @@
     return event
 
 
-def emit(source, event, data):
+def emit(source, event_name=u"", **data):
     """Convenience method to emit a message for an event.
 
-    :param source: Something, along with `event`, that can be adapted to
-        `ILongPollEvent`.
-    :param event: A name/key of the event that is emitted.
+    :param source: Something that can be be adapted to `ILongPollEvent`.
+    :param event_name: The name of the event to subscribe to. This is used to
+        look up a named adapter from `target` to `ILongPollEvent`.
+    :param data: See `ILongPollEvent.emit`.
     :return: The `ILongPollEvent` that has been emitted.
     """
-    event = getMultiAdapter((source, event), ILongPollEvent)
-    event.emit(data)
+    event = getAdapter(source, ILongPollEvent, name=event_name)
+    event.emit(**data)
     return event

=== modified file 'lib/lp/app/longpoll/tests/test_longpoll.py'
--- lib/lp/app/longpoll/tests/test_longpoll.py	2011-09-16 15:01:47 +0000
+++ lib/lp/app/longpoll/tests/test_longpoll.py	2011-09-21 09:20:47 +0000
@@ -45,19 +45,17 @@
 
 class FakeEvent:
 
-    adapts(IFakeObject, Interface)
+    adapts(IFakeObject)
     implements(ILongPollEvent)
 
-    def __init__(self, source, event):
+    def __init__(self, source):
         self.source = source
-        self.event = event
 
     @property
     def event_key(self):
-        return "event-key-%s-%s" % (
-            self.source.ident, self.event)
+        return "event-key-%s" % self.source.ident
 
-    def emit(self, data):
+    def emit(self, **data):
         # Don't cargo-cult this; see .adapters.event.LongPollEvent instead.
         RabbitRoutingKey(self.event_key).send_now(data)
 
@@ -67,38 +65,54 @@
     layer = LaunchpadFunctionalLayer
 
     def test_subscribe(self):
-        # subscribe() gets the ILongPollEvent for the given (target, event)
-        # and the ILongPollSubscriber for the given request (or the current
-        # request is discovered). It subscribes the latter to the event, then
-        # returns the event.
+        # subscribe() gets the ILongPollEvent for the given target and the
+        # ILongPollSubscriber for the given request (or the current request is
+        # discovered). It subscribes the latter to the event, then returns the
+        # event.
         request = LaunchpadTestRequest()
         an_object = FakeObject(12345)
         with ZopeAdapterFixture(FakeEvent):
-            event = subscribe(an_object, "foo", request=request)
+            event = subscribe(an_object, request=request)
         self.assertIsInstance(event, FakeEvent)
-        self.assertEqual("event-key-12345-foo", event.event_key)
+        self.assertEqual("event-key-12345", event.event_key)
         # Emitting an event-key-12345-foo event will put something on the
         # subscriber's queue.
         event_data = {"1234": 5678}
-        event.emit(event_data)
+        event.emit(**event_data)
         subscriber = ILongPollSubscriber(request)
         subscribe_queue = RabbitQueue(subscriber.subscribe_key)
         message = subscribe_queue.receive(timeout=5)
         self.assertEqual(event_data, message)
 
+    def test_subscribe_to_named_event(self):
+        # When an event_name is given to subscribe(), a named adapter is used
+        # to get the ILongPollEvent for the given target.
+        request = LaunchpadTestRequest()
+        an_object = FakeObject(12345)
+        with ZopeAdapterFixture(FakeEvent, name="foo"):
+            event = subscribe(an_object, event_name="foo", request=request)
+        self.assertIsInstance(event, FakeEvent)
+
     def test_emit(self):
-        # subscribe() gets the ILongPollEvent for the given (target, event)
-        # and passes the given data to its emit() method. It then returns the
-        # event.
+        # emit() gets the ILongPollEvent for the given target and passes the
+        # given data to its emit() method. It then returns the event.
         an_object = FakeObject(12345)
         with ZopeAdapterFixture(FakeEvent):
-            event = emit(an_object, "bar", {})
+            event = emit(an_object)
             routing_key = RabbitRoutingKey(event.event_key)
             subscribe_queue = RabbitQueue("whatever")
             routing_key.associateConsumer(subscribe_queue)
             # Emit the event again; the subscribe queue was not associated
             # with the event before now.
             event_data = {"8765": 4321}
-            event = emit(an_object, "bar", event_data)
+            event = emit(an_object, **event_data)
         message = subscribe_queue.receive(timeout=5)
         self.assertEqual(event_data, message)
+
+    def test_emit_named_event(self):
+        # When an event_name is given to emit(), a named adapter is used to
+        # get the ILongPollEvent for the given target.
+        an_object = FakeObject(12345)
+        with ZopeAdapterFixture(FakeEvent, name="foo"):
+            event = emit(an_object, "foo")
+        self.assertIsInstance(event, FakeEvent)

=== modified file 'lib/lp/services/longpoll/adapters/event.py'
--- lib/lp/services/longpoll/adapters/event.py	2011-09-16 14:41:21 +0000
+++ lib/lp/services/longpoll/adapters/event.py	2011-09-21 09:20:47 +0000
@@ -27,24 +27,40 @@
 class LongPollEvent:
     """Base-class for event adapters.
 
-    Sub-classes need to declare something along the lines of:
-
-        adapts(Interface, Interface)
-        implements(ILongPollEvent)
+    Sub-classes need to define the `event_key` property and declare something
+    along the lines of::
+
+        class LongPollAwesomeThingEvent(LongPollEvent):
+            adapts(IAwesomeThing)
+            implements(ILongPollEvent)
+
+    Alternatively, use the `long_poll_event` class decorator::
+
+        @long_poll_event(IAwesomeThing)
+        class LongPollAwesomeThingEvent(LongPollEvent):
+            ...
+
+    In both cases the adapter should be registered in a `configure.zcml`
+    somewhere sensible::
+
+        <adapter factory=".adapters.LongPollAwesomeThingEvent" />
 
     """
 
-    def __init__(self, source, event):
+    def __init__(self, source):
         self.source = source
-        self.event = event
 
     @property
     def event_key(self):
         """See `ILongPollEvent`."""
         raise NotImplementedError(self.__class__.event_key)
 
-    def emit(self, data):
-        """See `ILongPollEvent`."""
-        payload = {"event_key": self.event_key, "event_data": data}
-        router = router_factory(self.event_key)
-        router.send(payload)
+    def emit(self, **data):
+        """See `ILongPollEvent`.
+
+        The data will be updated with `event_key`, a copy of `self.event_key`.
+        """
+        event_key = self.event_key
+        data.update(event_key=event_key)
+        router = router_factory(event_key)
+        router.send(data)

=== modified file 'lib/lp/services/longpoll/adapters/storm.py'
--- lib/lp/services/longpoll/adapters/storm.py	2011-09-21 09:20:47 +0000
+++ lib/lp/services/longpoll/adapters/storm.py	2011-09-21 09:20:47 +0000
@@ -16,23 +16,24 @@
 from storm.base import Storm
 from storm.info import get_obj_info
 from zope.component import adapter
-from zope.interface import implements
 
 from lp.services.longpoll.adapters.event import (
     generate_event_key,
     LongPollEvent,
     )
-from lp.services.longpoll.interfaces import ILongPollEvent
-
-
+from lp.services.longpoll.interfaces import (
+    ILongPollEvent,
+    long_poll_event,
+    )
+
+
+@long_poll_event(Storm)
 class LongPollStormEvent(LongPollEvent):
     """A `ILongPollEvent` for events of `Storm` objects.
 
     This class knows how to construct a stable event key given a Storm object.
     """
 
-    implements(ILongPollEvent)
-
     @property
     def event_key(self):
         """See `ILongPollEvent`.
@@ -41,30 +42,29 @@
         Storm model object.
         """
         cls_info = get_obj_info(self.source).cls_info
-        key_parts = [cls_info.table.name.lower()]
-        key_parts.extend(
-            primary_key_column.__get__(self.source)
-            for primary_key_column in cls_info.primary_key)
-        key_parts.append(self.event)
-        return generate_event_key(*key_parts)
+        return generate_event_key(
+            cls_info.table.name.lower(), *(
+                primary_key_column.__get__(self.source)
+                for primary_key_column in cls_info.primary_key))
 
 
 @adapter(Storm, IObjectCreatedEvent)
 def object_created(model_instance, object_event):
     """Subscription handler for `Storm` creation events."""
-    event = LongPollStormEvent(model_instance, "created")
-    event.emit({})
+    event = ILongPollEvent(model_instance)
+    event.emit(what="created")
 
 
 @adapter(Storm, IObjectDeletedEvent)
 def object_deleted(model_instance, object_event):
     """Subscription handler for `Storm` deletion events."""
-    event = LongPollStormEvent(model_instance, "deleted")
-    event.emit({})
+    event = ILongPollEvent(model_instance)
+    event.emit(what="deleted")
 
 
 @adapter(Storm, IObjectModifiedEvent)
 def object_modified(model_instance, object_event):
     """Subscription handler for `Storm` modification events."""
-    event = LongPollStormEvent(model_instance, "modified")
-    event.emit({"edited_fields": sorted(object_event.edited_fields)})
+    edited_fields = sorted(object_event.edited_fields)
+    event = ILongPollEvent(model_instance)
+    event.emit(what="modified", edited_fields=edited_fields)

=== modified file 'lib/lp/services/longpoll/adapters/subscriber.py'
--- lib/lp/services/longpoll/adapters/subscriber.py	2011-09-16 14:59:47 +0000
+++ lib/lp/services/longpoll/adapters/subscriber.py	2011-09-21 09:20:47 +0000
@@ -6,7 +6,7 @@
 __metaclass__ = type
 __all__ = [
     "generate_subscribe_key",
-    "LongPollSubscriber",
+    "LongPollApplicationRequestSubscriber",
     ]
 
 from uuid import uuid4
@@ -28,7 +28,7 @@
     return "longpoll.subscribe.%s" % uuid4()
 
 
-class LongPollSubscriber:
+class LongPollApplicationRequestSubscriber:
 
     adapts(IApplicationRequest)
     implements(ILongPollSubscriber)

=== modified file 'lib/lp/services/longpoll/adapters/tests/test_event.py'
--- lib/lp/services/longpoll/adapters/tests/test_event.py	2011-09-16 14:59:47 +0000
+++ lib/lp/services/longpoll/adapters/tests/test_event.py	2011-09-21 09:20:47 +0000
@@ -7,10 +7,7 @@
 
 from zope.interface import implements
 
-from canonical.testing.layers import (
-    BaseLayer,
-    LaunchpadFunctionalLayer,
-    )
+from canonical.testing.layers import LaunchpadFunctionalLayer
 from lp.services.longpoll.adapters.event import (
     generate_event_key,
     LongPollEvent,
@@ -27,7 +24,7 @@
 
     @property
     def event_key(self):
-        return "event-key-%s-%s" % (self.source, self.event)
+        return "event-key-%s" % self.source
 
 
 class TestLongPollEvent(TestCase):
@@ -35,24 +32,21 @@
     layer = LaunchpadFunctionalLayer
 
     def test_interface(self):
-        event = FakeEvent("source", "event")
+        event = FakeEvent("source")
         self.assertProvides(event, ILongPollEvent)
 
     def test_event_key(self):
         # event_key is not implemented in LongPollEvent; subclasses must
         # provide it.
-        event = LongPollEvent("source", "event")
+        event = LongPollEvent("source")
         self.assertRaises(NotImplementedError, getattr, event, "event_key")
 
     def test_emit(self):
         # LongPollEvent.emit() sends the given data to `event_key`.
-        event = FakeEvent("source", "event")
+        event = FakeEvent("source")
         event_data = {"hello": 1234}
-        event.emit(event_data)
-        expected_message = {
-            "event_key": event.event_key,
-            "event_data": event_data,
-            }
+        event.emit(**event_data)
+        expected_message = dict(event_data, event_key=event.event_key)
         pending_messages = [
             message for (call, message) in
             RabbitMessageBase.class_locals.messages]
@@ -61,8 +55,6 @@
 
 class TestFunctions(TestCase):
 
-    layer = BaseLayer
-
     def test_generate_event_key_no_components(self):
         self.assertRaises(
             AssertionError, generate_event_key)

=== modified file 'lib/lp/services/longpoll/adapters/tests/test_storm.py'
--- lib/lp/services/longpoll/adapters/tests/test_storm.py	2011-09-21 09:20:47 +0000
+++ lib/lp/services/longpoll/adapters/tests/test_storm.py	2011-09-21 09:20:47 +0000
@@ -15,11 +15,13 @@
 from zope.event import notify
 
 from canonical.testing.layers import LaunchpadFunctionalLayer
+from lp.services.longpoll.interfaces import ILongPollEvent
 from lp.services.longpoll.testing import (
     capture_longpoll_emissions,
     LongPollEventRecord,
     )
 from lp.testing import TestCase
+from lp.testing.matchers import Provides
 
 
 class FakeStormClass(Storm):
@@ -33,42 +35,47 @@
 
     layer = LaunchpadFunctionalLayer
 
+    def test_storm_event_adapter(self):
+        storm_object = FakeStormClass()
+        storm_object.id = 1234
+        event = ILongPollEvent(storm_object)
+        self.assertThat(event, Provides(ILongPollEvent))
+        self.assertEqual(
+            "longpoll.event.faketable.1234",
+            event.event_key)
+
     def test_storm_object_created(self):
         storm_object = FakeStormClass()
         storm_object.id = 1234
         with capture_longpoll_emissions() as log:
             notify(ObjectCreatedEvent(storm_object))
-        expected = [
-            LongPollEventRecord(
-                "longpoll.event.faketable.1234.created",
-                {"event_key": "longpoll.event.faketable.1234.created",
-                 "event_data": {}}),
-            ]
-        self.assertEqual(expected, log)
+        expected = LongPollEventRecord(
+            "longpoll.event.faketable.1234",
+            {"event_key": "longpoll.event.faketable.1234",
+             "what": "created"})
+        self.assertEqual([expected], log)
 
     def test_storm_object_deleted(self):
         storm_object = FakeStormClass()
         storm_object.id = 1234
         with capture_longpoll_emissions() as log:
             notify(ObjectDeletedEvent(storm_object))
-        expected = [
-            LongPollEventRecord(
-                "longpoll.event.faketable.1234.deleted",
-                {"event_key": "longpoll.event.faketable.1234.deleted",
-                 "event_data": {}}),
-            ]
-        self.assertEqual(expected, log)
+        expected = LongPollEventRecord(
+            "longpoll.event.faketable.1234",
+            {"event_key": "longpoll.event.faketable.1234",
+             "what": "deleted"})
+        self.assertEqual([expected], log)
 
     def test_storm_object_modified(self):
         storm_object = FakeStormClass()
         storm_object.id = 1234
         with capture_longpoll_emissions() as log:
-            notify(ObjectModifiedEvent(
-                    storm_object, storm_object, ("itchy", "scratchy")))
-        expected = [
-            LongPollEventRecord(
-                "longpoll.event.faketable.1234.modified",
-                {"event_key": "longpoll.event.faketable.1234.modified",
-                 "event_data": {"edited_fields": ["itchy", "scratchy"]}}),
-            ]
-        self.assertEqual(expected, log)
+            object_event = ObjectModifiedEvent(
+                storm_object, storm_object, ("itchy", "scratchy"))
+            notify(object_event)
+        expected = LongPollEventRecord(
+            "longpoll.event.faketable.1234",
+            {"event_key": "longpoll.event.faketable.1234",
+             "what": "modified",
+             "edited_fields": ["itchy", "scratchy"]})
+        self.assertEqual([expected], log)

=== modified file 'lib/lp/services/longpoll/adapters/tests/test_subscriber.py'
--- lib/lp/services/longpoll/adapters/tests/test_subscriber.py	2011-09-16 14:59:47 +0000
+++ lib/lp/services/longpoll/adapters/tests/test_subscriber.py	2011-09-21 09:20:47 +0000
@@ -15,13 +15,10 @@
 from zope.interface import implements
 
 from canonical.launchpad.webapp.servers import LaunchpadTestRequest
-from canonical.testing.layers import (
-    BaseLayer,
-    LaunchpadFunctionalLayer,
-    )
+from canonical.testing.layers import LaunchpadFunctionalLayer
 from lp.services.longpoll.adapters.subscriber import (
     generate_subscribe_key,
-    LongPollSubscriber,
+    LongPollApplicationRequestSubscriber,
     )
 from lp.services.longpoll.interfaces import (
     ILongPollEvent,
@@ -51,12 +48,12 @@
 
     def test_interface(self):
         request = LaunchpadTestRequest()
-        subscriber = LongPollSubscriber(request)
+        subscriber = LongPollApplicationRequestSubscriber(request)
         self.assertProvides(subscriber, ILongPollSubscriber)
 
     def test_subscribe_key(self):
         request = LaunchpadTestRequest()
-        subscriber = LongPollSubscriber(request)
+        subscriber = LongPollApplicationRequestSubscriber(request)
         # A subscribe key is not generated yet.
         self.assertIs(subscriber.subscribe_key, None)
         # It it only generated on the first subscription.
@@ -71,7 +68,8 @@
     def test_adapter(self):
         request = LaunchpadTestRequest()
         subscriber = ILongPollSubscriber(request)
-        self.assertIsInstance(subscriber, LongPollSubscriber)
+        self.assertIsInstance(
+            subscriber, LongPollApplicationRequestSubscriber)
         # A difference subscriber is returned on subsequent adaptions, but it
         # has the same subscribe_key.
         subscriber2 = ILongPollSubscriber(request)
@@ -79,8 +77,8 @@
         self.assertEqual(subscriber.subscribe_key, subscriber2.subscribe_key)
 
     def test_subscribe_queue(self):
-        # LongPollSubscriber.subscribe() creates a new queue with a new unique
-        # name that is bound to the event's event_key.
+        # LongPollApplicationRequestSubscriber.subscribe() creates a new queue
+        # with a new unique name that is bound to the event's event_key.
         request = LaunchpadTestRequest()
         event = FakeEvent()
         subscriber = ILongPollSubscriber(request)
@@ -93,8 +91,8 @@
             message, subscribe_queue.receive(timeout=5))
 
     def test_json_cache_not_populated_on_init(self):
-        # LongPollSubscriber does not put the name of the new queue into the
-        # JSON cache.
+        # LongPollApplicationRequestSubscriber does not put the name of the
+        # new queue into the JSON cache.
         request = LaunchpadTestRequest()
         cache = IJSONRequestCache(request)
         self.assertThat(cache.objects, Not(Contains("longpoll")))
@@ -124,8 +122,6 @@
 
 class TestFunctions(TestCase):
 
-    layer = BaseLayer
-
     def test_generate_subscribe_key(self):
         subscribe_key = generate_subscribe_key()
         expected_prefix = "longpoll.subscribe."

=== modified file 'lib/lp/services/longpoll/configure.zcml'
--- lib/lp/services/longpoll/configure.zcml	2011-09-21 09:20:47 +0000
+++ lib/lp/services/longpoll/configure.zcml	2011-09-21 09:20:47 +0000
@@ -6,7 +6,8 @@
     xmlns:browser="http://namespaces.zope.org/browser";
     xmlns:i18n="http://namespaces.zope.org/i18n";
     i18n_domain="launchpad">
-    <adapter factory=".adapters.subscriber.LongPollSubscriber" />
+    <adapter factory=".adapters.storm.LongPollStormEvent" />
+    <adapter factory=".adapters.subscriber.LongPollApplicationRequestSubscriber" />
     <subscriber handler=".adapters.storm.object_created" />
     <subscriber handler=".adapters.storm.object_deleted" />
     <subscriber handler=".adapters.storm.object_modified" />

=== modified file 'lib/lp/services/longpoll/interfaces.py'
--- lib/lp/services/longpoll/interfaces.py	2011-09-20 10:14:41 +0000
+++ lib/lp/services/longpoll/interfaces.py	2011-09-21 09:20:47 +0000
@@ -22,20 +22,14 @@
 
     source = Attribute("The event source.")
 
-    event = Attribute("An object indicating the type of event.")
-
     event_key = Attribute(
         "The key with which events will be emitted. Should be predictable "
         "and stable.")
 
-    def emit(data):
+    def emit(**data):
         """Emit the given data to `event_key`.
 
-        The data will be wrapped up into a `dict` with the keys `event_key`
-        and `event_data`, where `event_key` is a copy of `self.event_key` and
-        `event_data` is the `data` argument.
-
-        :param data: Any data structure that can be dumped as JSON.
+        :param data: Any data structures that can be dumped as JSON.
         """
 
 
@@ -53,19 +47,14 @@
         """
 
 
-def long_poll_event(source_spec, event_spec=basestring):
+def long_poll_event(source_spec):
     """Class decorator to declare an `ILongPollEvent`.
 
     :param source_spec: An interface or other specification understood by
         `zope.component` (a plain class can be passed too) that defines the
         source of an event. `IJob` or `storm.base.Storm` for example.
-    :param source_event: An interface or other specification understood by
-        `zope.component`. The exact use here is left to implementers. By
-        default it is `basestring` so that terms like "modified" or
-        "lifecycle" can be used when looking up the event, but it could also
-        be `IObjectModifiedEvent`. The dominant use case is evolving.
     """
-    declare_adapter = adapter(source_spec, event_spec)
+    declare_adapter = adapter(source_spec)
 
     def declare_event(cls):
         classImplements(cls, ILongPollEvent)

=== modified file 'lib/lp/services/longpoll/tests/test_interfaces.py'
--- lib/lp/services/longpoll/tests/test_interfaces.py	2011-09-16 16:07:59 +0000
+++ lib/lp/services/longpoll/tests/test_interfaces.py	2011-09-21 09:20:47 +0000
@@ -19,30 +19,15 @@
     """Test interface for an event source."""
 
 
-class IEventSpecifierInterface(Interface):
-    """Test interface for an event specifier."""
-
-
 class TestLongPollInterfaces(TestCase):
 
     def test_long_poll_event(self):
         # long_poll_event is a class decorator that declares a class as an
         # ILongPollEvent.
-        @long_poll_event(IEventSourceInterface, IEventSpecifierInterface)
-        class Something:
-            """An example event source."""
-        self.assertTrue(ILongPollEvent.implementedBy(Something))
-        self.assertEqual(
-            (IEventSourceInterface, IEventSpecifierInterface),
-            adaptedBy(Something))
-
-    def test_long_poll_event_default(self):
-        # By default, long_poll_event assumes that the event spec is
-        # basestring.
         @long_poll_event(IEventSourceInterface)
         class Something:
             """An example event source."""
         self.assertTrue(ILongPollEvent.implementedBy(Something))
         self.assertEqual(
-            (IEventSourceInterface, basestring),
+            (IEventSourceInterface,),
             adaptedBy(Something))