@@ -95,6 +95,9 @@ informal introduction to the features and their implementation.
9595 - [ Worker Shutdown] ( #worker-shutdown )
9696 - [ Testing] ( #testing-1 )
9797 - [ Nexus] ( #nexus )
98+ - [ Plugins] ( #plugins )
99+ - [ Client Plugins] ( #client-plugins )
100+ - [ Worker Plugins] ( #worker-plugins )
98101 - [ Workflow Replay] ( #workflow-replay )
99102 - [ Observability] ( #observability )
100103 - [ Metrics] ( #metrics )
@@ -1416,6 +1419,138 @@ https://github.com/temporalio/samples-python/tree/nexus/hello_nexus).
14161419 ```
14171420
14181421
1422+ ### Plugins
1423+
1424+ Plugins provide a way to extend and customize the behavior of Temporal clients and workers through a chain of
1425+ responsibility pattern. They allow you to intercept and modify client creation, service connections, worker
1426+ configuration, and worker execution. Common customizations may include but are not limited to:
1427+
1428+ 1. DataConverter
1429+ 2. Activities
1430+ 3. Workflows
1431+ 4. Interceptors
1432+
1433+ A single plugin class can implement both client and worker plugin interfaces to share common logic between both
1434+ contexts. When used with a client, it will automatically be propagated to any workers created with that client.
1435+
1436+ #### Client Plugins
1437+
1438+ Client plugins can intercept and modify client configuration and service connections. They are useful for adding
1439+ authentication, modifying connection parameters, or adding custom behavior during client creation.
1440+
1441+ Here's an example of a client plugin that adds custom authentication:
1442+
1443+ ```python
1444+ from temporalio.client import Plugin, ClientConfig
1445+ import temporalio.service
1446+
1447+ class AuthenticationPlugin(Plugin):
1448+ def __init__(self, api_key: str):
1449+ self.api_key = api_key
1450+
1451+ def on_create_client(self, config: ClientConfig) -> ClientConfig:
1452+ # Modify client configuration
1453+ config["namespace"] = "my-secure-namespace"
1454+ return super().on_create_client(config)
1455+
1456+ async def connect_service_client(
1457+ self, config: temporalio.service.ConnectConfig
1458+ ) -> temporalio.service.ServiceClient:
1459+ # Add authentication to the connection
1460+ config.api_key = self.api_key
1461+ return await super().connect_service_client(config)
1462+
1463+ # Use the plugin when connecting
1464+ client = await Client.connect(
1465+ "my-server.com:7233",
1466+ plugins=[AuthenticationPlugin("my-api-key")]
1467+ )
1468+ ```
1469+
1470+ #### Worker Plugins
1471+
1472+ Worker plugins can modify worker configuration and intercept worker execution. They are useful for adding monitoring,
1473+ custom lifecycle management, or modifying worker settings.
1474+
1475+ Here's an example of a worker plugin that adds custom monitoring:
1476+
1477+ ``` python
1478+ from temporalio.worker import Plugin, WorkerConfig, Worker
1479+ import logging
1480+
1481+ class MonitoringPlugin (Plugin ):
1482+ def __init__ (self ):
1483+ self .logger = logging.getLogger(__name__ )
1484+
1485+ def on_create_worker (self , config : WorkerConfig) -> WorkerConfig:
1486+ # Modify worker configuration
1487+ original_task_queue = config[" task_queue" ]
1488+ config[" task_queue" ] = f " monitored- { original_task_queue} "
1489+ self .logger.info(f " Worker created for task queue: { config[' task_queue' ]} " )
1490+ return super ().on_create_worker(config)
1491+
1492+ async def run_worker (self , worker : Worker) -> None :
1493+ self .logger.info(" Starting worker execution" )
1494+ try :
1495+ await super ().run_worker(worker)
1496+ finally :
1497+ self .logger.info(" Worker execution completed" )
1498+
1499+ # Use the plugin when creating a worker
1500+ worker = Worker(
1501+ client,
1502+ task_queue = " my-task-queue" ,
1503+ workflows = [MyWorkflow],
1504+ activities = [my_activity],
1505+ plugins = [MonitoringPlugin()]
1506+ )
1507+ ```
1508+
1509+ For plugins that need to work with both clients and workers, you can implement both interfaces in a single class:
1510+
1511+ ``` python
1512+ from temporalio.client import Plugin as ClientPlugin
1513+ from temporalio.worker import Plugin as WorkerPlugin
1514+
1515+ class UnifiedPlugin (ClientPlugin , WorkerPlugin ):
1516+ def on_create_client (self , config : ClientConfig) -> ClientConfig:
1517+ # Client-side customization
1518+ config[" namespace" ] = " unified-namespace"
1519+ return super ().on_create_client(config)
1520+
1521+ def on_create_worker (self , config : WorkerConfig) -> WorkerConfig:
1522+ # Worker-side customization
1523+ config[" max_cached_workflows" ] = 500
1524+ return super ().on_create_worker(config)
1525+
1526+ async def run_worker (self , worker : Worker) -> None :
1527+ print (" Starting unified worker" )
1528+ await super ().run_worker(worker)
1529+
1530+ # Create client with the unified plugin
1531+ client = await Client.connect(
1532+ " localhost:7233" ,
1533+ plugins = [UnifiedPlugin()]
1534+ )
1535+
1536+ # Worker will automatically inherit the plugin from the client
1537+ worker = Worker(
1538+ client,
1539+ task_queue = " my-task-queue" ,
1540+ workflows = [MyWorkflow],
1541+ activities = [my_activity]
1542+ )
1543+ ```
1544+
1545+ ** Important Notes:**
1546+
1547+ - Plugins are executed in reverse order (last plugin wraps the first), forming a chain of responsibility
1548+ - Client plugins that also implement worker plugin interfaces are automatically propagated to workers
1549+ - Avoid providing the same plugin to both client and worker to prevent double execution
1550+ - Plugin methods should call ` super() ` to maintain the plugin chain
1551+ - Each plugin's ` name() ` method returns a unique identifier for debugging purposes
1552+
1553+
14191554### Workflow Replay
14201555
14211556Given a workflow's history, it can be replayed locally to check for things like non-determinism errors. For example,
0 commit comments