3535)
3636
3737import temporalio .common
38+ import temporalio .converter
3839
3940from .types import CallableType
4041
@@ -51,11 +52,19 @@ def defn(
5152 ...
5253
5354
55+ @overload
56+ def defn (
57+ * , no_thread_cancel_exception : bool = False , dynamic : bool = False
58+ ) -> Callable [[CallableType ], CallableType ]:
59+ ...
60+
61+
5462def defn (
5563 fn : Optional [CallableType ] = None ,
5664 * ,
5765 name : Optional [str ] = None ,
5866 no_thread_cancel_exception : bool = False ,
67+ dynamic : bool = False ,
5968):
6069 """Decorator for activity functions.
6170
@@ -64,15 +73,19 @@ def defn(
6473 Args:
6574 fn: The function to decorate.
6675 name: Name to use for the activity. Defaults to function ``__name__``.
76+ This cannot be set if dynamic is set.
6777 no_thread_cancel_exception: If set to true, an exception will not be
6878 raised in synchronous, threaded activities upon cancellation.
79+ dynamic: If true, this activity will be dynamic. Dynamic activities have
80+ to accept a single 'Sequence[RawValue]' parameter. This cannot be
81+ set to true if name is present.
6982 """
7083
7184 def decorator (fn : CallableType ) -> CallableType :
7285 # This performs validation
7386 _Definition ._apply_to_callable (
7487 fn ,
75- activity_name = name or fn .__name__ ,
88+ activity_name = name or fn .__name__ if not dynamic else None ,
7689 no_thread_cancel_exception = no_thread_cancel_exception ,
7790 )
7891 return fn
@@ -132,7 +145,12 @@ class _Context:
132145 cancelled_event : _CompositeEvent
133146 worker_shutdown_event : _CompositeEvent
134147 shield_thread_cancel_exception : Optional [Callable [[], AbstractContextManager ]]
148+ payload_converter_class_or_instance : Union [
149+ Type [temporalio .converter .PayloadConverter ],
150+ temporalio .converter .PayloadConverter ,
151+ ]
135152 _logger_details : Optional [Mapping [str , Any ]] = None
153+ _payload_converter : Optional [temporalio .converter .PayloadConverter ] = None
136154
137155 @staticmethod
138156 def current () -> _Context :
@@ -155,6 +173,18 @@ def logger_details(self) -> Mapping[str, Any]:
155173 self ._logger_details = self .info ()._logger_details ()
156174 return self ._logger_details
157175
176+ @property
177+ def payload_converter (self ) -> temporalio .converter .PayloadConverter :
178+ if not self ._payload_converter :
179+ if isinstance (
180+ self .payload_converter_class_or_instance ,
181+ temporalio .converter .PayloadConverter ,
182+ ):
183+ self ._payload_converter = self .payload_converter_class_or_instance
184+ else :
185+ self ._payload_converter = self .payload_converter_class_or_instance ()
186+ return self ._payload_converter
187+
158188
159189@dataclass
160190class _CompositeEvent :
@@ -339,6 +369,14 @@ class _CompleteAsyncError(BaseException):
339369 pass
340370
341371
372+ def payload_converter () -> temporalio .converter .PayloadConverter :
373+ """Get the payload converter for the current activity.
374+
375+ This is often used for dynamic activities to convert payloads.
376+ """
377+ return _Context .current ().payload_converter
378+
379+
342380class LoggerAdapter (logging .LoggerAdapter ):
343381 """Adapter that adds details to the log about the running activity.
344382
@@ -387,9 +425,9 @@ def base_logger(self) -> logging.Logger:
387425"""Logger that will have contextual activity details embedded."""
388426
389427
390- @dataclass
428+ @dataclass ( frozen = True )
391429class _Definition :
392- name : str
430+ name : Optional [ str ]
393431 fn : Callable
394432 is_async : bool
395433 no_thread_cancel_exception : bool
@@ -420,7 +458,10 @@ def must_from_callable(fn: Callable) -> _Definition:
420458
421459 @staticmethod
422460 def _apply_to_callable (
423- fn : Callable , * , activity_name : str , no_thread_cancel_exception : bool = False
461+ fn : Callable ,
462+ * ,
463+ activity_name : Optional [str ],
464+ no_thread_cancel_exception : bool = False ,
424465 ) -> None :
425466 # Validate the activity
426467 if hasattr (fn , "__temporal_activity_definition" ):
@@ -447,6 +488,16 @@ def _apply_to_callable(
447488
448489 def __post_init__ (self ) -> None :
449490 if self .arg_types is None and self .ret_type is None :
491+ dynamic = self .name is None
450492 arg_types , ret_type = temporalio .common ._type_hints_from_func (self .fn )
493+ # If dynamic, must be a sequence of raw values
494+ if dynamic and (
495+ not arg_types
496+ or len (arg_types ) != 1
497+ or arg_types [0 ] != Sequence [temporalio .common .RawValue ]
498+ ):
499+ raise TypeError (
500+ "Dynamic activity must accept a single Sequence[temporalio.common.RawValue]"
501+ )
451502 object .__setattr__ (self , "arg_types" , arg_types )
452503 object .__setattr__ (self , "ret_type" , ret_type )
0 commit comments