launchpad-reviewers team mailing list archive
-
launchpad-reviewers team
-
Mailing list archive
-
Message #05186
[Merge] lp:~allenap/launchpad/longpoll-creation-crazy into lp:launchpad
Gavin Panella has proposed merging lp:~allenap/launchpad/longpoll-creation-crazy into lp:launchpad.
Requested reviews:
Launchpad code reviewers (launchpad-reviewers)
For more details, see:
https://code.launchpad.net/~allenap/launchpad/longpoll-creation-crazy/+merge/78394
This branch makes it possible to subscribe to Storm life-cycle
*creation* events via the Storm model class:
subscribe(lp.bugs.model.Bug)
ensures that subscribers will receive events like:
{'what': 'created', 'primary_key': 23, 'event_key': 'longpoll.event.bug'}
for every Bug for which IObjectCreatedEvent is fired.
It also adds the primary key to the modified and deleted events that
are sent out, just like the "id" key in the event above. This is a
somewhat temporary measure until we can put the devel API link into
the event.
--
https://code.launchpad.net/~allenap/launchpad/longpoll-creation-crazy/+merge/78394
Your team Launchpad code reviewers is requested to review the proposed merge of lp:~allenap/launchpad/longpoll-creation-crazy into lp:launchpad.
=== modified file 'lib/lp/services/longpoll/adapters/storm.py'
--- lib/lp/services/longpoll/adapters/storm.py 2011-09-26 11:26:25 +0000
+++ lib/lp/services/longpoll/adapters/storm.py 2011-10-06 12:22:44 +0000
@@ -14,9 +14,13 @@
IObjectModifiedEvent,
)
from storm.base import Storm
-from storm.info import get_obj_info
+from storm.info import (
+ get_cls_info,
+ get_obj_info,
+ )
from zope.component import adapter
from zope.interface.interfaces import IAttribute
+from zope.security.proxy import removeSecurityProxy
from lp.services.longpoll.adapters.event import (
generate_event_key,
@@ -28,6 +32,23 @@
)
+def gen_primary_key(model_instance):
+ """Generate the primary key values for the given model instance."""
+ cls_info = get_obj_info(model_instance).cls_info
+ for primary_key_column in cls_info.primary_key:
+ yield primary_key_column.__get__(model_instance)
+
+
+def get_primary_key(model_instance):
+ """Return the primary key for the given model instance.
+
+ If the primary key contains only one value it is returned, otherwise all
+ the primary key values are returned in a tuple.
+ """
+ pkey = tuple(gen_primary_key(model_instance))
+ return pkey[0] if len(pkey) == 1 else pkey
+
+
@long_poll_event(Storm)
class LongPollStormEvent(LongPollEvent):
"""A `ILongPollEvent` for events of `Storm` objects.
@@ -44,23 +65,41 @@
"""
cls_info = get_obj_info(self.source).cls_info
return generate_event_key(
- cls_info.table.name.lower(), *(
- primary_key_column.__get__(self.source)
- for primary_key_column in cls_info.primary_key))
+ cls_info.table.name.lower(),
+ *gen_primary_key(self.source))
+
+
+@long_poll_event(type(Storm))
+class LongPollStormCreationEvent(LongPollEvent):
+ """A `ILongPollEvent` for events of `Storm` *classes*.
+
+ This class knows how to construct a stable event key given a Storm class.
+ """
+
+ @property
+ def event_key(self):
+ """See `ILongPollEvent`.
+
+ Constructs the key from the table name of the Storm class.
+ """
+ cls_info = get_cls_info(self.source)
+ return generate_event_key(
+ cls_info.table.name.lower())
@adapter(Storm, IObjectCreatedEvent)
def object_created(model_instance, object_event):
"""Subscription handler for `Storm` creation events."""
- event = ILongPollEvent(model_instance)
- event.emit(what="created")
+ model_class = removeSecurityProxy(model_instance).__class__
+ event = ILongPollEvent(model_class)
+ event.emit(what="created", id=get_primary_key(model_instance))
@adapter(Storm, IObjectDeletedEvent)
def object_deleted(model_instance, object_event):
"""Subscription handler for `Storm` deletion events."""
event = ILongPollEvent(model_instance)
- event.emit(what="deleted")
+ event.emit(what="deleted", id=get_primary_key(model_instance))
@adapter(Storm, IObjectModifiedEvent)
@@ -72,4 +111,6 @@
(field.__name__ if IAttribute.providedBy(field) else field)
for field in edited_fields)
event = ILongPollEvent(model_instance)
- event.emit(what="modified", edited_fields=edited_field_names)
+ event.emit(
+ what="modified", edited_fields=edited_field_names,
+ id=get_primary_key(model_instance))
=== modified file 'lib/lp/services/longpoll/adapters/tests/test_storm.py'
--- lib/lp/services/longpoll/adapters/tests/test_storm.py 2011-09-26 11:26:25 +0000
+++ lib/lp/services/longpoll/adapters/tests/test_storm.py 2011-10-06 12:22:44 +0000
@@ -16,6 +16,10 @@
from zope.interface import Attribute
from canonical.testing.layers import LaunchpadFunctionalLayer
+from lp.services.longpoll.adapters.storm import (
+ gen_primary_key,
+ get_primary_key,
+ )
from lp.services.longpoll.interfaces import ILongPollEvent
from lp.services.longpoll.testing import (
capture_longpoll_emissions,
@@ -32,6 +36,48 @@
id = Int(primary=True)
+class FakeStormCompoundPrimaryKeyClass(Storm):
+
+ __storm_table__ = 'FakeTableWithCompoundPrimaryKey'
+ __storm_primary__ = 'id1', 'id2'
+
+ id1 = Int()
+ id2 = Int()
+
+
+class TestFunctions(TestCase):
+
+ def test_gen_primary_key(self):
+ # gen_primary_key() returns an iterable of values from the model
+ # instance's primary key.
+ storm_object = FakeStormClass()
+ storm_object.id = 1234
+ self.assertEqual([1234], list(gen_primary_key(storm_object)))
+
+ def test_gen_primary_key_compound_key(self):
+ # gen_primary_key() returns an iterable of values from the model
+ # instance's primary key.
+ storm_object = FakeStormCompoundPrimaryKeyClass()
+ storm_object.id1 = 1234
+ storm_object.id2 = 5678
+ self.assertEqual([1234, 5678], list(gen_primary_key(storm_object)))
+
+ def test_get_primary_key(self):
+ # get_primary_key() returns the value of the model instance's primary
+ # key.
+ storm_object = FakeStormClass()
+ storm_object.id = 1234
+ self.assertEqual(1234, get_primary_key(storm_object))
+
+ def test_get_primary_key_compound_key(self):
+ # get_primary_key() returns a tuple of all the values in the model
+ # instance's primary key when the model uses a compound primary key.
+ storm_object = FakeStormCompoundPrimaryKeyClass()
+ storm_object.id1 = 1234
+ storm_object.id2 = 5678
+ self.assertEqual((1234, 5678), get_primary_key(storm_object))
+
+
class TestStormLifecycle(TestCase):
layer = LaunchpadFunctionalLayer
@@ -45,15 +91,23 @@
"longpoll.event.faketable.1234",
event.event_key)
+ def test_storm_creation_event_adapter(self):
+ event = ILongPollEvent(FakeStormClass)
+ self.assertThat(event, Provides(ILongPollEvent))
+ self.assertEqual(
+ "longpoll.event.faketable",
+ 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", {
- "event_key": "longpoll.event.faketable.1234",
+ "longpoll.event.faketable", {
+ "event_key": "longpoll.event.faketable",
"what": "created",
+ "id": 1234,
})
self.assertEqual([expected], log)
@@ -66,6 +120,7 @@
"longpoll.event.faketable.1234", {
"event_key": "longpoll.event.faketable.1234",
"what": "deleted",
+ "id": 1234,
})
self.assertEqual([expected], log)
@@ -81,6 +136,7 @@
"event_key": "longpoll.event.faketable.1234",
"what": "modified",
"edited_fields": ["itchy", "scratchy"],
+ "id": 1234,
})
self.assertEqual([expected], log)
@@ -109,5 +165,6 @@
"event_key": "longpoll.event.faketable.1234",
"what": "modified",
"edited_fields": ["bar", "foo"],
+ "id": 1234,
})
self.assertEqual([expected], log)
=== modified file 'lib/lp/services/longpoll/configure.zcml'
--- lib/lp/services/longpoll/configure.zcml 2011-09-21 08:10:16 +0000
+++ lib/lp/services/longpoll/configure.zcml 2011-10-06 12:22:44 +0000
@@ -7,6 +7,7 @@
xmlns:i18n="http://namespaces.zope.org/i18n"
i18n_domain="launchpad">
<adapter factory=".adapters.storm.LongPollStormEvent" />
+ <adapter factory=".adapters.storm.LongPollStormCreationEvent" />
<adapter factory=".adapters.subscriber.LongPollApplicationRequestSubscriber" />
<subscriber handler=".adapters.storm.object_created" />
<subscriber handler=".adapters.storm.object_deleted" />