Skip to content

Commit b0209de

Browse files
committed
#797: add sortable 'Message.timestamp' property.
Allows subscribers of timestamping topics to sort messages.
1 parent 627e0de commit b0209de

3 files changed

Lines changed: 72 additions & 8 deletions

File tree

gcloud/pubsub/message.py

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,11 @@
1515
"""Define API Topics."""
1616

1717
import base64
18+
import datetime
19+
20+
import pytz
21+
22+
RFC3369 = '%Y-%m-%dT%H:%M:%S.%fZ'
1823

1924

2025
class Message(object):
@@ -44,6 +49,20 @@ def attributes(self):
4449
self._attributes = {}
4550
return self._attributes
4651

52+
@property
53+
def timestamp(self):
54+
"""Return timestamp from attributes, if passed.
55+
56+
:rtype: datetime
57+
:returns: timestamp (in UTC timezone) parsed from RFC 3369 timestamp
58+
:raises: ValueError if timestamp not in ``attributes``, or malformed
59+
"""
60+
stamp = self.attributes.get('timestamp')
61+
if stamp is None:
62+
raise ValueError('No timestamp')
63+
return datetime.datetime.strptime(stamp, RFC3369).replace(
64+
tzinfo=pytz.UTC)
65+
4766
@classmethod
4867
def from_api_repr(cls, api_repr):
4968
"""Factory: construct message from API representation.

gcloud/pubsub/test_message.py

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,3 +66,39 @@ def test_from_api_repr_w_attributes(self):
6666
self.assertEqual(message.data, DATA)
6767
self.assertEqual(message.message_id, MESSAGE_ID)
6868
self.assertEqual(message.attributes, ATTRS)
69+
70+
def test_timestamp_no_attributes(self):
71+
DATA = b'DEADBEEF'
72+
MESSAGE_ID = b'12345'
73+
message = self._makeOne(data=DATA, message_id=MESSAGE_ID)
74+
75+
def _to_fail():
76+
return message.timestamp
77+
78+
self.assertRaises(ValueError, _to_fail)
79+
80+
def test_timestamp_wo_timestamp_in_attributes(self):
81+
DATA = b'DEADBEEF'
82+
MESSAGE_ID = b'12345'
83+
ATTRS = {'a': 'b'}
84+
message = self._makeOne(data=DATA, message_id=MESSAGE_ID,
85+
attributes=ATTRS)
86+
87+
def _to_fail():
88+
return message.timestamp
89+
90+
self.assertRaises(ValueError, _to_fail)
91+
92+
def test_timestamp_w_timestamp_in_attributes(self):
93+
from datetime import datetime
94+
import pytz
95+
DATA = b'DEADBEEF'
96+
MESSAGE_ID = b'12345'
97+
TIMESTAMP = '2015-04-10T18:42:27.131956Z'
98+
RFC3369 = '%Y-%m-%dT%H:%M:%S.%fZ'
99+
naive = datetime.strptime(TIMESTAMP, RFC3369)
100+
timestamp = naive.replace(tzinfo=pytz.utc)
101+
ATTRS = {'timestamp': TIMESTAMP}
102+
message = self._makeOne(data=DATA, message_id=MESSAGE_ID,
103+
attributes=ATTRS)
104+
self.assertEqual(message.timestamp, timestamp)

regression/pubsub.py

Lines changed: 17 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -103,7 +103,7 @@ def test_list_subscriptions(self):
103103

104104
def test_message_pull_mode_e2e(self):
105105
TOPIC_NAME = 'subscribe-me'
106-
topic = Topic(TOPIC_NAME)
106+
topic = Topic(TOPIC_NAME, timestamp_messages=True)
107107
self.assertFalse(topic.exists())
108108
topic.create()
109109
self.to_delete.append(topic)
@@ -113,14 +113,23 @@ def test_message_pull_mode_e2e(self):
113113
subscription.create()
114114
self.to_delete.append(subscription)
115115

116-
MESSAGE = b'MESSAGE'
117-
EXTRA = 'EXTRA'
118-
topic.publish(MESSAGE, extra=EXTRA)
116+
MESSAGE_1 = b'MESSAGE ONE'
117+
MESSAGE_2 = b'MESSAGE ONE'
118+
EXTRA_1 = 'EXTRA 1'
119+
EXTRA_2 = 'EXTRA 2'
120+
topic.publish(MESSAGE_1, extra=EXTRA_1)
121+
topic.publish(MESSAGE_2, extra=EXTRA_2)
119122

120-
received = subscription.pull()
123+
received = subscription.pull(max_messages=2)
121124
ack_ids = [recv[0] for recv in received]
122125
subscription.acknowledge(ack_ids)
123126
messages = [recv[1] for recv in received]
124-
message, = messages
125-
self.assertEqual(message.data, MESSAGE)
126-
self.assertEqual(message.attributes, {'extra': EXTRA})
127+
128+
def _by_timestamp(message):
129+
return message.timestamp
130+
131+
message1, message2 = sorted(messages, key=_by_timestamp)
132+
self.assertEqual(message1.data, MESSAGE_1)
133+
self.assertEqual(message1.attributes['extra'], EXTRA_1)
134+
self.assertEqual(message2.data, MESSAGE_2)
135+
self.assertEqual(message2.attributes['extra'], EXTRA_2)

0 commit comments

Comments
 (0)