Skip to content

Commit 85944e7

Browse files
feat: Make message_id encode a PublishMetadata which includes the partition (#90)
* feat: Make message_id encode a PublishMetadata which includes the partition * feat: Make message_id encode a PublishMetadata which includes the partition
1 parent aa7105d commit 85944e7

File tree

6 files changed

+106
-18
lines changed

6 files changed

+106
-18
lines changed

google/cloud/pubsublite/cloudpubsub/internal/make_subscriber.py

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,11 @@
1717

1818
from google.api_core.client_options import ClientOptions
1919
from google.auth.credentials import Credentials
20+
21+
from google.cloud.pubsublite.cloudpubsub.message_transforms import (
22+
to_cps_subscribe_message,
23+
add_id_to_cps_subscribe_transformer,
24+
)
2025
from google.cloud.pubsublite.types import FlowControlSettings
2126
from google.cloud.pubsublite.cloudpubsub.internal.ack_set_tracker_impl import (
2227
AckSetTrackerImpl,
@@ -28,10 +33,7 @@
2833
from google.cloud.pubsublite.cloudpubsub.internal.single_partition_subscriber import (
2934
SinglePartitionSingleSubscriber,
3035
)
31-
from google.cloud.pubsublite.cloudpubsub.message_transformer import (
32-
MessageTransformer,
33-
DefaultMessageTransformer,
34-
)
36+
from google.cloud.pubsublite.cloudpubsub.message_transformer import MessageTransformer
3537
from google.cloud.pubsublite.cloudpubsub.nack_handler import (
3638
NackHandler,
3739
DefaultNackHandler,
@@ -149,7 +151,7 @@ def cursor_connection_factory(
149151
flow_control_settings,
150152
ack_set_tracker,
151153
nack_handler,
152-
message_transformer,
154+
add_id_to_cps_subscribe_transformer(partition, message_transformer),
153155
)
154156

155157
return factory
@@ -200,7 +202,7 @@ def make_async_subscriber(
200202
if nack_handler is None:
201203
nack_handler = DefaultNackHandler()
202204
if message_transformer is None:
203-
message_transformer = DefaultMessageTransformer()
205+
message_transformer = MessageTransformer.of_callable(to_cps_subscribe_message)
204206
partition_subscriber_factory = _make_partition_subscriber_factory(
205207
subscription,
206208
transport,

google/cloud/pubsublite/cloudpubsub/message_transformer.py

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -13,12 +13,11 @@
1313
# limitations under the License.
1414

1515
from abc import ABC, abstractmethod
16+
from typing import Callable
1617

1718
from google.pubsub_v1 import PubsubMessage
19+
from overrides import overrides
1820

19-
from google.cloud.pubsublite.cloudpubsub.message_transforms import (
20-
to_cps_subscribe_message,
21-
)
2221
from google.cloud.pubsublite_v1 import SequencedMessage
2322

2423

@@ -39,7 +38,11 @@ def transform(self, source: SequencedMessage) -> PubsubMessage:
3938
"""
4039
pass
4140

41+
@staticmethod
42+
def of_callable(transformer: Callable[[SequencedMessage], PubsubMessage]):
43+
class CallableTransformer(MessageTransformer):
44+
@overrides
45+
def transform(self, source: SequencedMessage) -> PubsubMessage:
46+
return transformer(source)
4247

43-
class DefaultMessageTransformer(MessageTransformer):
44-
def transform(self, source: SequencedMessage) -> PubsubMessage:
45-
return to_cps_subscribe_message(source)
48+
return CallableTransformer()

google/cloud/pubsublite/cloudpubsub/message_transforms.py

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@
1818
from google.protobuf.timestamp_pb2 import Timestamp
1919
from google.pubsub_v1 import PubsubMessage
2020

21+
from google.cloud.pubsublite.cloudpubsub import MessageTransformer
22+
from google.cloud.pubsublite.types import Partition, PublishMetadata
2123
from google.cloud.pubsublite_v1 import AttributeValues, SequencedMessage, PubSubMessage
2224

2325
PUBSUB_LITE_EVENT_TIME = "x-goog-pubsublite-event-time"
@@ -52,9 +54,23 @@ def _parse_attributes(values: AttributeValues) -> str:
5254
)
5355

5456

57+
def add_id_to_cps_subscribe_transformer(
58+
partition: Partition, transformer: MessageTransformer
59+
) -> MessageTransformer:
60+
def add_id_to_message(source: SequencedMessage):
61+
message: PubsubMessage = transformer.transform(source)
62+
if message.message_id:
63+
raise InvalidArgument(
64+
"Message after transforming has the message_id field set."
65+
)
66+
message.message_id = PublishMetadata(partition, source.cursor).encode()
67+
return message
68+
69+
return MessageTransformer.of_callable(add_id_to_message)
70+
71+
5572
def to_cps_subscribe_message(source: SequencedMessage) -> PubsubMessage:
5673
message: PubsubMessage = to_cps_publish_message(source.message)
57-
message.message_id = str(source.cursor.offset)
5874
message.publish_time = source.publish_time
5975
return message
6076

google/cloud/pubsublite/cloudpubsub/subscriber_client.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,7 @@ def __init__(
7474
Args:
7575
executor: A ThreadPoolExecutor to use. The client will shut it down on __exit__. If provided a single threaded executor, messages will be ordered per-partition, but take care that the callback does not block for too long as it will impede forward progress on all subscriptions.
7676
nack_handler: A handler for when `nack()` is called. The default NackHandler raises an exception and fails the subscribe stream.
77-
message_transformer: A transformer from Pub/Sub Lite messages to Cloud Pub/Sub messages.
77+
message_transformer: A transformer from Pub/Sub Lite messages to Cloud Pub/Sub messages. This may not return a message with "message_id" set.
7878
credentials: If provided, the credentials to use when connecting.
7979
transport: The transport to use. Must correspond to an asyncio transport.
8080
client_options: The client options to use when connecting. If used, must explicitly set `api_endpoint`.
@@ -151,7 +151,7 @@ def __init__(
151151
152152
Args:
153153
nack_handler: A handler for when `nack()` is called. The default NackHandler raises an exception and fails the subscribe stream.
154-
message_transformer: A transformer from Pub/Sub Lite messages to Cloud Pub/Sub messages.
154+
message_transformer: A transformer from Pub/Sub Lite messages to Cloud Pub/Sub messages. This may not return a message with "message_id" set.
155155
credentials: If provided, the credentials to use when connecting.
156156
transport: The transport to use. Must correspond to an asyncio transport.
157157
client_options: The client options to use when connecting. If used, must explicitly set `api_endpoint`.

samples/snippets/subscriber_example.py

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,10 @@
2121

2222
import argparse
2323

24+
from google.pubsub_v1 import PubsubMessage
25+
26+
from google.cloud.pubsublite.types import PublishMetadata
27+
2428

2529
def receive_messages(
2630
project_number, cloud_region, zone_id, subscription_id, timeout=90
@@ -54,9 +58,10 @@ def receive_messages(
5458
bytes_outstanding=10 * 1024 * 1024,
5559
)
5660

57-
def callback(message):
61+
def callback(message: PubsubMessage):
5862
message_data = message.data.decode("utf-8")
59-
print(f"Received {message_data} of ordering key {message.ordering_key}.")
63+
metadata = PublishMetadata.decode(message.message_id)
64+
print(f"Received {message_data} of ordering key {message.ordering_key} with id {metadata}.")
6065
message.ack()
6166

6267
# SubscriberClient() must be used in a `with` block or have __enter__() called before use.

tests/unit/pubsublite/cloudpubsub/message_transforms_test.py

Lines changed: 63 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,12 +19,15 @@
1919
from google.protobuf.timestamp_pb2 import Timestamp
2020
from google.pubsub_v1 import PubsubMessage
2121

22+
from google.cloud.pubsublite.cloudpubsub import MessageTransformer
2223
from google.cloud.pubsublite.cloudpubsub.message_transforms import (
2324
PUBSUB_LITE_EVENT_TIME,
2425
to_cps_subscribe_message,
2526
encode_attribute_event_time,
2627
from_cps_publish_message,
28+
add_id_to_cps_subscribe_transformer,
2729
)
30+
from google.cloud.pubsublite.types import Partition, PublishMetadata
2831
from google.cloud.pubsublite_v1 import (
2932
SequencedMessage,
3033
Cursor,
@@ -104,7 +107,6 @@ def test_subscribe_transform_correct():
104107
Timestamp(seconds=55).ToDatetime()
105108
),
106109
},
107-
message_id=str(10),
108110
publish_time=Timestamp(seconds=10),
109111
)
110112
result = to_cps_subscribe_message(
@@ -126,6 +128,66 @@ def test_subscribe_transform_correct():
126128
assert result == expected
127129

128130

131+
def test_wrapped_sets_id_error():
132+
wrapped = add_id_to_cps_subscribe_transformer(
133+
Partition(1),
134+
MessageTransformer.of_callable(lambda x: PubsubMessage(message_id="a")),
135+
)
136+
with pytest.raises(InvalidArgument):
137+
wrapped.transform(
138+
SequencedMessage(
139+
message=PubSubMessage(
140+
data=b"xyz",
141+
key=b"def",
142+
event_time=Timestamp(seconds=55),
143+
attributes={
144+
"x": AttributeValues(values=[b"abc"]),
145+
"y": AttributeValues(values=[b"abc"]),
146+
},
147+
),
148+
publish_time=Timestamp(seconds=10),
149+
cursor=Cursor(offset=10),
150+
size_bytes=10,
151+
)
152+
)
153+
154+
155+
def test_wrapped_successful():
156+
wrapped = add_id_to_cps_subscribe_transformer(
157+
Partition(1), MessageTransformer.of_callable(to_cps_subscribe_message)
158+
)
159+
expected = PubsubMessage(
160+
data=b"xyz",
161+
ordering_key="def",
162+
attributes={
163+
"x": "abc",
164+
"y": "abc",
165+
PUBSUB_LITE_EVENT_TIME: encode_attribute_event_time(
166+
Timestamp(seconds=55).ToDatetime()
167+
),
168+
},
169+
message_id=PublishMetadata(Partition(1), Cursor(offset=10)).encode(),
170+
publish_time=Timestamp(seconds=10),
171+
)
172+
result = wrapped.transform(
173+
SequencedMessage(
174+
message=PubSubMessage(
175+
data=b"xyz",
176+
key=b"def",
177+
event_time=Timestamp(seconds=55),
178+
attributes={
179+
"x": AttributeValues(values=[b"abc"]),
180+
"y": AttributeValues(values=[b"abc"]),
181+
},
182+
),
183+
publish_time=Timestamp(seconds=10),
184+
cursor=Cursor(offset=10),
185+
size_bytes=10,
186+
)
187+
)
188+
assert result == expected
189+
190+
129191
def test_publish_invalid_event_time():
130192
with pytest.raises(InvalidArgument):
131193
from_cps_publish_message(

0 commit comments

Comments
 (0)