2525from dapr .clients ._constants import DEFAULT_JSON_CONTENT_TYPE
2626from dapr .clients .grpc ._request import BindingRequest , InvokeMethodRequest , JobEvent
2727from dapr .clients .grpc ._response import InvokeMethodResponse , TopicEventResponse
28+ from dapr .common .pubsub .subscription import SubscriptionMessage
2829from dapr .proto import appcallback_service_v1 , appcallback_v1 , common_v1
2930from dapr .proto .common .v1 .common_pb2 import InvokeRequest
3031from dapr .proto .runtime .v1 .appcallback_pb2 import (
3637)
3738
3839InvokeMethodCallable = Callable [[InvokeMethodRequest ], Union [str , bytes , InvokeMethodResponse ]]
39- TopicSubscribeCallable = Callable [[v1 .Event ], Optional [TopicEventResponse ]]
40+ TopicSubscribeCallable = Callable [
41+ [Union [v1 .Event , SubscriptionMessage ]], Optional [TopicEventResponse ]
42+ ]
4043BindingCallable = Callable [[BindingRequest ], None ]
4144JobEventCallable = Callable [[JobEvent ], None ]
4245
@@ -74,6 +77,7 @@ class _CallbackServicer(
7477 def __init__ (self ):
7578 self ._invoke_method_map : Dict [str , InvokeMethodCallable ] = {}
7679 self ._topic_map : Dict [str , TopicSubscribeCallable ] = {}
80+ self ._topic_legacy_event : Dict [TopicSubscribeCallable , bool ] = {}
7781 self ._binding_map : Dict [str , BindingCallable ] = {}
7882 self ._job_event_map : Dict [str , JobEventCallable ] = {}
7983
@@ -120,8 +124,15 @@ def register_topic(
120124 dead_letter_topic : Optional [str ] = None ,
121125 rule : Optional [Rule ] = None ,
122126 disable_topic_validation : Optional [bool ] = False ,
127+ legacy_cloudevent : bool = True ,
123128 ) -> None :
124- """Registers topic subscription for pubsub."""
129+ """Registers topic subscription for pubsub.
130+
131+ Args:
132+ legacy_cloudevent (bool): when True (deprecated default), the handler receives a
133+ ``cloudevents.sdk.event.v1.Event``; when False, it receives a
134+ :class:`dapr.common.pubsub.subscription.SubscriptionMessage`.
135+ """
125136 topic_key = pubsub_name + DELIMITER + topic
126137 pubsub_topic = topic_key + DELIMITER
127138 if rule is not None :
@@ -130,6 +141,7 @@ def register_topic(
130141 if pubsub_topic in self ._topic_map :
131142 raise ValueError (f'{ topic } is already registered with { pubsub_name } ' )
132143 self ._topic_map [pubsub_topic ] = cb
144+ self ._topic_legacy_event [cb ] = legacy_cloudevent
133145 routing_path = path if rule is not None else topic
134146 self ._route_map [(pubsub_name , routing_path )] = cb
135147
@@ -233,21 +245,27 @@ def OnTopicEvent(self, request: TopicEventRequest, context):
233245 context .set_code (grpc .StatusCode .UNIMPLEMENTED ) # type: ignore
234246 raise NotImplementedError (f'topic { request .topic } is not implemented!' )
235247
236- customdata : Struct = request .extensions
237- extensions = dict ()
238- for k , v in customdata .items ():
239- extensions [k ] = v
240- for k , v in context .invocation_metadata ():
241- extensions ['_metadata_' + k ] = v
242-
243- event = v1 .Event ()
244- event .SetEventType (request .type )
245- event .SetEventID (request .id )
246- event .SetSource (request .source )
247- event .SetData (request .data )
248- event .SetContentType (request .data_content_type )
249- event .SetSubject (request .topic )
250- event .SetExtensions (extensions )
248+ invocation_metadata = dict (context .invocation_metadata ())
249+
250+ event : Union [v1 .Event , SubscriptionMessage ]
251+ if self ._topic_legacy_event .get (cb , True ):
252+ customdata : Struct = request .extensions
253+ extensions = dict ()
254+ for k , v in customdata .items ():
255+ extensions [k ] = v
256+ for k , v in invocation_metadata .items ():
257+ extensions ['_metadata_' + k ] = v
258+
259+ event = v1 .Event ()
260+ event .SetEventType (request .type )
261+ event .SetEventID (request .id )
262+ event .SetSource (request .source )
263+ event .SetData (request .data )
264+ event .SetContentType (request .data_content_type )
265+ event .SetSubject (request .topic )
266+ event .SetExtensions (extensions )
267+ else :
268+ event = SubscriptionMessage (request , invocation_metadata )
251269
252270 response = cb (event )
253271 if isinstance (response , TopicEventResponse ):
@@ -323,35 +341,20 @@ def _handle_bulk_topic_event(
323341 if cb is None :
324342 return None # we don't have a handler
325343
344+ use_legacy_event = self ._topic_legacy_event .get (cb , True )
345+ invocation_metadata = dict (context .invocation_metadata ())
346+
326347 statuses = []
327348 for entry in request .entries :
328349 entry_id = entry .entry_id
329350 try :
330- # Build event from entry & send req with many entries
331- event = v1 .Event ()
332- extensions = dict ()
333- if entry .HasField ('cloud_event' ) and entry .cloud_event :
334- ce = entry .cloud_event
335- event .SetEventType (ce .type )
336- event .SetEventID (ce .id )
337- event .SetSource (ce .source )
338- event .SetData (ce .data )
339- event .SetContentType (ce .data_content_type )
340- if ce .extensions :
341- for k , v in ce .extensions .items ():
342- extensions [k ] = v
351+ event : Union [v1 .Event , SubscriptionMessage ]
352+ if use_legacy_event :
353+ event = self ._bulk_entry_legacy_event (entry , request , invocation_metadata )
343354 else :
344- event .SetEventID (entry_id )
345- event .SetData (entry .bytes if entry .HasField ('bytes' ) else b'' )
346- event .SetContentType (entry .content_type or '' )
347- event .SetSubject (request .topic )
348- if entry .metadata :
349- for k , v in entry .metadata .items ():
350- extensions [k ] = v
351- for k , v in context .invocation_metadata ():
352- extensions ['_metadata_' + k ] = v
353- if extensions :
354- event .SetExtensions (extensions )
355+ event = self ._bulk_entry_subscription_message (
356+ entry , request , invocation_metadata
357+ )
355358
356359 response = cb (event ) # invoke app registered handler and send event
357360 if isinstance (response , TopicEventResponse ):
@@ -365,6 +368,70 @@ def _handle_bulk_topic_event(
365368 )
366369 return appcallback_v1 .TopicEventBulkResponse (statuses = statuses )
367370
371+ def _bulk_entry_legacy_event (
372+ self ,
373+ entry ,
374+ request : TopicEventBulkRequest ,
375+ invocation_metadata : Dict [str , str ],
376+ ) -> v1 .Event :
377+ """Builds the deprecated cloudevents v1.Event for a bulk entry."""
378+ event = v1 .Event ()
379+ extensions = dict ()
380+ if entry .HasField ('cloud_event' ) and entry .cloud_event :
381+ ce = entry .cloud_event
382+ event .SetEventType (ce .type )
383+ event .SetEventID (ce .id )
384+ event .SetSource (ce .source )
385+ event .SetData (ce .data )
386+ event .SetContentType (ce .data_content_type )
387+ if ce .extensions :
388+ for k , v in ce .extensions .items ():
389+ extensions [k ] = v
390+ else :
391+ event .SetEventID (entry .entry_id )
392+ event .SetData (entry .bytes if entry .HasField ('bytes' ) else b'' )
393+ event .SetContentType (entry .content_type or '' )
394+ event .SetSubject (request .topic )
395+ if entry .metadata :
396+ for k , v in entry .metadata .items ():
397+ extensions [k ] = v
398+ for k , v in invocation_metadata .items ():
399+ extensions ['_metadata_' + k ] = v
400+ if extensions :
401+ event .SetExtensions (extensions )
402+ return event
403+
404+ def _bulk_entry_subscription_message (
405+ self ,
406+ entry ,
407+ request : TopicEventBulkRequest ,
408+ invocation_metadata : Dict [str , str ],
409+ ) -> SubscriptionMessage :
410+ """Builds a SubscriptionMessage for a bulk entry via a synthesized TopicEventRequest."""
411+ if entry .HasField ('cloud_event' ) and entry .cloud_event :
412+ ce = entry .cloud_event
413+ entry_request = TopicEventRequest (
414+ id = ce .id ,
415+ source = ce .source ,
416+ type = ce .type ,
417+ spec_version = ce .spec_version ,
418+ data_content_type = ce .data_content_type ,
419+ data = ce .data ,
420+ topic = request .topic ,
421+ pubsub_name = request .pubsub_name ,
422+ extensions = ce .extensions ,
423+ )
424+ else :
425+ entry_request = TopicEventRequest (
426+ id = entry .entry_id ,
427+ data = entry .bytes if entry .HasField ('bytes' ) else b'' ,
428+ data_content_type = entry .content_type or '' ,
429+ topic = request .topic ,
430+ pubsub_name = request .pubsub_name ,
431+ )
432+ metadata = {** invocation_metadata , ** dict (entry .metadata )}
433+ return SubscriptionMessage (entry_request , metadata )
434+
368435 def OnBulkTopicEvent (self , request : TopicEventBulkRequest , context ):
369436 """Subscribes bulk events from Pubsub"""
370437 response = self ._handle_bulk_topic_event (request , context )
0 commit comments