|
4 | 4 | [](https://pypi.org/project/temporalio) |
5 | 5 | [](LICENSE) |
6 | 6 |
|
| 7 | +**📣 News: Integration between OpenAI Agents SDK and Temporal is now in public preview. [Learn more](temporalio/contrib/openai_agents/README.md).** |
| 8 | + |
7 | 9 | [Temporal](https://temporal.io/) is a distributed, scalable, durable, and highly available orchestration engine used to |
8 | 10 | execute asynchronous, long-running business logic in a scalable and resilient way. |
9 | 11 |
|
@@ -96,6 +98,9 @@ informal introduction to the features and their implementation. |
96 | 98 | - [Testing](#testing-1) |
97 | 99 | - [Interceptors](#interceptors) |
98 | 100 | - [Nexus](#nexus) |
| 101 | + - [Plugins](#plugins) |
| 102 | + - [Client Plugins](#client-plugins) |
| 103 | + - [Worker Plugins](#worker-plugins) |
99 | 104 | - [Workflow Replay](#workflow-replay) |
100 | 105 | - [Observability](#observability) |
101 | 106 | - [Metrics](#metrics) |
@@ -1482,6 +1487,140 @@ https://github.com/temporalio/samples-python/tree/nexus/hello_nexus). |
1482 | 1487 | ``` |
1483 | 1488 |
|
1484 | 1489 |
|
| 1490 | +### Plugins |
| 1491 | +
|
| 1492 | +Plugins provide a way to extend and customize the behavior of Temporal clients and workers through a chain of |
| 1493 | +responsibility pattern. They allow you to intercept and modify client creation, service connections, worker |
| 1494 | +configuration, and worker execution. Common customizations may include but are not limited to: |
| 1495 | +
|
| 1496 | +1. DataConverter |
| 1497 | +2. Activities |
| 1498 | +3. Workflows |
| 1499 | +4. Interceptors |
| 1500 | +
|
| 1501 | +A single plugin class can implement both client and worker plugin interfaces to share common logic between both |
| 1502 | +contexts. When used with a client, it will automatically be propagated to any workers created with that client. |
| 1503 | +
|
| 1504 | +#### Client Plugins |
| 1505 | +
|
| 1506 | +Client plugins can intercept and modify client configuration and service connections. They are useful for adding |
| 1507 | +authentication, modifying connection parameters, or adding custom behavior during client creation. |
| 1508 | +
|
| 1509 | +Here's an example of a client plugin that adds custom authentication: |
| 1510 | +
|
| 1511 | +```python |
| 1512 | +from temporalio.client import Plugin, ClientConfig |
| 1513 | +import temporalio.service |
| 1514 | +
|
| 1515 | +class AuthenticationPlugin(Plugin): |
| 1516 | + def __init__(self, api_key: str): |
| 1517 | + self.api_key = api_key |
| 1518 | +
|
| 1519 | + def configure_client(self, config: ClientConfig) -> ClientConfig: |
| 1520 | + # Modify client configuration |
| 1521 | + config["namespace"] = "my-secure-namespace" |
| 1522 | + return super().configure_client(config) |
| 1523 | +
|
| 1524 | + async def connect_service_client( |
| 1525 | + self, config: temporalio.service.ConnectConfig |
| 1526 | + ) -> temporalio.service.ServiceClient: |
| 1527 | + # Add authentication to the connection |
| 1528 | + config.api_key = self.api_key |
| 1529 | + return await super().connect_service_client(config) |
| 1530 | +
|
| 1531 | +# Use the plugin when connecting |
| 1532 | +client = await Client.connect( |
| 1533 | + "my-server.com:7233", |
| 1534 | + plugins=[AuthenticationPlugin("my-api-key")] |
| 1535 | +) |
| 1536 | +``` |
| 1537 | + |
| 1538 | +#### Worker Plugins |
| 1539 | + |
| 1540 | +Worker plugins can modify worker configuration and intercept worker execution. They are useful for adding monitoring, |
| 1541 | +custom lifecycle management, or modifying worker settings. |
| 1542 | + |
| 1543 | +Here's an example of a worker plugin that adds custom monitoring: |
| 1544 | + |
| 1545 | +```python |
| 1546 | +from temporalio.worker import Plugin, WorkerConfig, Worker |
| 1547 | +import logging |
| 1548 | + |
| 1549 | +class MonitoringPlugin(Plugin): |
| 1550 | + def __init__(self): |
| 1551 | + self.logger = logging.getLogger(__name__) |
| 1552 | + |
| 1553 | + def configure_worker(self, config: WorkerConfig) -> WorkerConfig: |
| 1554 | + # Modify worker configuration |
| 1555 | + original_task_queue = config["task_queue"] |
| 1556 | + config["task_queue"] = f"monitored-{original_task_queue}" |
| 1557 | + self.logger.info(f"Worker created for task queue: {config['task_queue']}") |
| 1558 | + return super().configure_worker(config) |
| 1559 | + |
| 1560 | + async def run_worker(self, worker: Worker) -> None: |
| 1561 | + self.logger.info("Starting worker execution") |
| 1562 | + try: |
| 1563 | + await super().run_worker(worker) |
| 1564 | + finally: |
| 1565 | + self.logger.info("Worker execution completed") |
| 1566 | + |
| 1567 | +# Use the plugin when creating a worker |
| 1568 | +worker = Worker( |
| 1569 | + client, |
| 1570 | + task_queue="my-task-queue", |
| 1571 | + workflows=[MyWorkflow], |
| 1572 | + activities=[my_activity], |
| 1573 | + plugins=[MonitoringPlugin()] |
| 1574 | +) |
| 1575 | +``` |
| 1576 | + |
| 1577 | +For plugins that need to work with both clients and workers, you can implement both interfaces in a single class: |
| 1578 | + |
| 1579 | +```python |
| 1580 | +from temporalio.client import Plugin as ClientPlugin, ClientConfig |
| 1581 | +from temporalio.worker import Plugin as WorkerPlugin, WorkerConfig |
| 1582 | + |
| 1583 | + |
| 1584 | +class UnifiedPlugin(ClientPlugin, WorkerPlugin): |
| 1585 | + def configure_client(self, config: ClientConfig) -> ClientConfig: |
| 1586 | + # Client-side customization |
| 1587 | + config["namespace"] = "unified-namespace" |
| 1588 | + return super().configure_client(config) |
| 1589 | + |
| 1590 | + def configure_worker(self, config: WorkerConfig) -> WorkerConfig: |
| 1591 | + # Worker-side customization |
| 1592 | + config["max_cached_workflows"] = 500 |
| 1593 | + return super().configure_worker(config) |
| 1594 | + |
| 1595 | + async def run_worker(self, worker: Worker) -> None: |
| 1596 | + print("Starting unified worker") |
| 1597 | + await super().run_worker(worker) |
| 1598 | + |
| 1599 | + |
| 1600 | +# Create client with the unified plugin |
| 1601 | +client = await Client.connect( |
| 1602 | + "localhost:7233", |
| 1603 | + plugins=[UnifiedPlugin()] |
| 1604 | +) |
| 1605 | + |
| 1606 | +# Worker will automatically inherit the plugin from the client |
| 1607 | +worker = Worker( |
| 1608 | + client, |
| 1609 | + task_queue="my-task-queue", |
| 1610 | + workflows=[MyWorkflow], |
| 1611 | + activities=[my_activity] |
| 1612 | +) |
| 1613 | +``` |
| 1614 | + |
| 1615 | +**Important Notes:** |
| 1616 | + |
| 1617 | +- Plugins are executed in reverse order (last plugin wraps the first), forming a chain of responsibility |
| 1618 | +- Client plugins that also implement worker plugin interfaces are automatically propagated to workers |
| 1619 | +- Avoid providing the same plugin to both client and worker to prevent double execution |
| 1620 | +- Plugin methods should call `super()` to maintain the plugin chain |
| 1621 | +- Each plugin's `name()` method returns a unique identifier for debugging purposes |
| 1622 | + |
| 1623 | + |
1485 | 1624 | ### Workflow Replay |
1486 | 1625 |
|
1487 | 1626 | Given a workflow's history, it can be replayed locally to check for things like non-determinism errors. For example, |
|
0 commit comments