77import logging
88from contextlib import asynccontextmanager
99from dataclasses import dataclass
10- from typing import AsyncIterator , Dict , Mapping , Optional , Sequence , Type , Union , cast
10+ from typing import AsyncIterator , Dict , Mapping , Optional , Sequence , Type , cast
1111
1212from typing_extensions import TypedDict
1313
1818import temporalio .converter
1919import temporalio .runtime
2020import temporalio .workflow
21- from temporalio .client import ClientConfig
2221
2322from ..common import HeaderCodecBehavior
2423from ._interceptor import Interceptor
3029logger = logging .getLogger (__name__ )
3130
3231
32+ class ReplayerPlugin :
33+ """Base class for replayer plugins that can modify replayer configuration."""
34+
35+ def configure_replayer (self , config : ReplayerConfig ) -> ReplayerConfig :
36+ """Configure the replayer.
37+
38+ Default implementation applies shared configuration from worker and client plugins.
39+
40+ Args:
41+ config: The replayer configuration to modify.
42+
43+ Returns:
44+ The modified replayer configuration.
45+ """
46+ # If this plugin is also a worker plugin, apply shared worker config
47+ if isinstance (self , temporalio .worker .Plugin ):
48+ # Create a minimal worker config with shared fields
49+ worker_config = cast (
50+ WorkerConfig ,
51+ {
52+ "workflows" : config ["workflows" ],
53+ "workflow_task_executor" : config ["workflow_task_executor" ],
54+ "workflow_runner" : config ["workflow_runner" ],
55+ "unsandboxed_workflow_runner" : config [
56+ "unsandboxed_workflow_runner"
57+ ],
58+ "interceptors" : config ["interceptors" ],
59+ "build_id" : config ["build_id" ],
60+ "identity" : config ["identity" ],
61+ "workflow_failure_exception_types" : config [
62+ "workflow_failure_exception_types"
63+ ],
64+ "debug_mode" : config ["debug_mode" ],
65+ "disable_safe_workflow_eviction" : config [
66+ "disable_safe_workflow_eviction"
67+ ],
68+ },
69+ )
70+
71+ modified_worker_config = self .configure_worker (worker_config )
72+ config ["workflows" ] = modified_worker_config ["workflows" ]
73+ config ["workflow_task_executor" ] = modified_worker_config [
74+ "workflow_task_executor"
75+ ]
76+ config ["workflow_runner" ] = modified_worker_config ["workflow_runner" ]
77+ config ["unsandboxed_workflow_runner" ] = modified_worker_config [
78+ "unsandboxed_workflow_runner"
79+ ]
80+ config ["interceptors" ] = modified_worker_config ["interceptors" ]
81+ config ["build_id" ] = modified_worker_config ["build_id" ]
82+ config ["identity" ] = modified_worker_config ["identity" ]
83+ config ["workflow_failure_exception_types" ] = modified_worker_config [
84+ "workflow_failure_exception_types"
85+ ]
86+ config ["debug_mode" ] = modified_worker_config ["debug_mode" ]
87+ config ["disable_safe_workflow_eviction" ] = modified_worker_config [
88+ "disable_safe_workflow_eviction"
89+ ]
90+
91+ # If this plugin is also a client plugin, apply shared client config
92+ if isinstance (self , temporalio .client .Plugin ):
93+ # Only include fields that exist in both ReplayerConfig and ClientConfig
94+ # Note: interceptors are different types between client and worker, so excluded
95+ client_config = cast (
96+ temporalio .client .ClientConfig ,
97+ {
98+ "namespace" : config ["namespace" ],
99+ "data_converter" : config ["data_converter" ],
100+ "header_codec_behavior" : config ["header_codec_behavior" ],
101+ },
102+ )
103+
104+ modified_client_config = self .configure_client (client_config )
105+ config ["namespace" ] = modified_client_config ["namespace" ]
106+ config ["data_converter" ] = modified_client_config ["data_converter" ]
107+ config ["header_codec_behavior" ] = modified_client_config [
108+ "header_codec_behavior"
109+ ]
110+
111+ return config
112+
113+
33114class Replayer :
34115 """Replayer to replay workflows from history."""
35116
@@ -43,9 +124,7 @@ def __init__(
43124 namespace : str = "ReplayNamespace" ,
44125 data_converter : temporalio .converter .DataConverter = temporalio .converter .DataConverter .default ,
45126 interceptors : Sequence [Interceptor ] = [],
46- plugins : Sequence [
47- Union [temporalio .worker .Plugin , temporalio .client .Plugin ]
48- ] = [],
127+ plugins : Sequence [ReplayerPlugin ] = [],
49128 build_id : Optional [str ] = None ,
50129 identity : Optional [str ] = None ,
51130 workflow_failure_exception_types : Sequence [Type [BaseException ]] = [],
@@ -85,62 +164,35 @@ def __init__(
85164 header_codec_behavior = header_codec_behavior ,
86165 )
87166
88- # Allow plugins to configure shared configurations with worker
89- root_worker_plugin : temporalio .worker .Plugin = temporalio .worker ._worker ._RootPlugin ()
90- for plugin in reversed (
167+ # Initialize all worker plugins
168+ root_worker_plugin : temporalio .worker .Plugin = (
169+ temporalio .worker ._worker ._RootPlugin ()
170+ )
171+ for worker_plugin in reversed (
91172 [
92- plugin
173+ cast ( temporalio . worker . Plugin , plugin )
93174 for plugin in plugins
94175 if isinstance (plugin , temporalio .worker .Plugin )
95176 ]
96177 ):
97- root_worker_plugin = plugin .init_worker_plugin (root_worker_plugin )
98-
99- worker_config = cast (
100- WorkerConfig ,
101- {
102- k : v
103- for k , v in self ._config .items ()
104- if k in WorkerConfig .__annotations__
105- },
106- )
107-
108- worker_config = root_worker_plugin .configure_worker (worker_config )
109- self ._config .update (
110- cast (ReplayerConfig , {
111- k : v
112- for k , v in worker_config .items ()
113- if k in ReplayerConfig .__annotations__
114- })
115- )
178+ root_worker_plugin = worker_plugin .init_worker_plugin (root_worker_plugin )
116179
117- # Allow plugins to configure shared configurations with client
180+ # Initialize all client plugins
118181 root_client_plugin : temporalio .client .Plugin = temporalio .client ._RootPlugin ()
119182 for client_plugin in reversed (
120183 [
121- plugin
184+ cast ( temporalio . client . Plugin , plugin )
122185 for plugin in plugins
123186 if isinstance (plugin , temporalio .client .Plugin )
124187 ]
125188 ):
126189 root_client_plugin = client_plugin .init_client_plugin (root_client_plugin )
127190
128- client_config = cast (ClientConfig ,
129- {
130- k : v
131- for k , v in self ._config .items ()
132- if k in ClientConfig .__annotations__
133- }
134- )
135- client_config = root_client_plugin .configure_client (client_config )
136- self ._config .update (
137- cast (ReplayerConfig , {
138- k : v
139- for k , v in client_config .items ()
140- if k in ReplayerConfig .__annotations__
141- })
142- )
191+ # Apply plugin configuration
192+ for plugin in plugins :
193+ self ._config = plugin .configure_replayer (self ._config )
143194
195+ # Validate workflows after plugin configuration
144196 if not self ._config ["workflows" ]:
145197 raise ValueError ("At least one workflow must be specified" )
146198
0 commit comments