Skip to content

Commit a203bf7

Browse files
committed
Merge remote-tracking branch 'upstream/main' into fix/opentelemetry-trace-context-propagation
2 parents c9b4b13 + 62604ca commit a203bf7

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

49 files changed

+4600
-1260
lines changed

.github/workflows/build-binaries.yml

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -74,8 +74,3 @@ jobs:
7474
with:
7575
name: packages-${{ matrix.package-suffix }}
7676
path: dist
77-
78-
- name: Deliberately fail to prevent releasing nexus-rpc w/ GitHub link in pyproject.toml
79-
run: |
80-
echo "This is a deliberate failure to prevent releasing nexus-rpc with a GitHub link in pyproject.toml"
81-
exit 1

README.md

Lines changed: 139 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@
44
[![PyPI](https://img.shields.io/pypi/v/temporalio.svg?style=for-the-badge)](https://pypi.org/project/temporalio)
55
[![MIT](https://img.shields.io/pypi/l/temporalio.svg?style=for-the-badge)](LICENSE)
66

7+
**📣 News: Integration between OpenAI Agents SDK and Temporal is now in public preview. [Learn more](temporalio/contrib/openai_agents/README.md).**
8+
79
[Temporal](https://temporal.io/) is a distributed, scalable, durable, and highly available orchestration engine used to
810
execute asynchronous, long-running business logic in a scalable and resilient way.
911

@@ -96,6 +98,9 @@ informal introduction to the features and their implementation.
9698
- [Testing](#testing-1)
9799
- [Interceptors](#interceptors)
98100
- [Nexus](#nexus)
101+
- [Plugins](#plugins)
102+
- [Client Plugins](#client-plugins)
103+
- [Worker Plugins](#worker-plugins)
99104
- [Workflow Replay](#workflow-replay)
100105
- [Observability](#observability)
101106
- [Metrics](#metrics)
@@ -1482,6 +1487,140 @@ https://github.com/temporalio/samples-python/tree/nexus/hello_nexus).
14821487
```
14831488
14841489
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+
14851624
### Workflow Replay
14861625

14871626
Given a workflow's history, it can be replayed locally to check for things like non-determinism errors. For example,

pyproject.toml

Lines changed: 5 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[project]
22
name = "temporalio"
3-
version = "1.14.1"
3+
version = "1.15.0"
44
description = "Temporal.io Python SDK"
55
authors = [{ name = "Temporal Technologies Inc", email = "[email protected]" }]
66
requires-python = ">=3.9"
@@ -11,7 +11,7 @@ keywords = [
1111
"workflow",
1212
]
1313
dependencies = [
14-
"nexus-rpc>=1.1.0",
14+
"nexus-rpc==1.1.0",
1515
"protobuf>=3.20,<6",
1616
"python-dateutil>=2.8.2,<3 ; python_version < '3.11'",
1717
"types-protobuf>=3.20",
@@ -26,7 +26,7 @@ opentelemetry = [
2626
]
2727
pydantic = ["pydantic>=2.0.0,<3"]
2828
openai-agents = [
29-
"openai-agents >= 0.1,<0.2",
29+
"openai-agents >= 0.2.3,<0.3",
3030
"eval-type-backport>=0.2.2; python_version < '3.10'"
3131
]
3232

@@ -57,6 +57,7 @@ dev = [
5757
"pytest-cov>=6.1.1",
5858
"httpx>=0.28.1",
5959
"pytest-pretty>=1.3.0",
60+
"openai-agents[litellm] >= 0.2.3,<0.3"
6061
]
6162

6263
[tool.poe.tasks]
@@ -165,6 +166,7 @@ reportAny = "none"
165166
reportCallInDefaultInitializer = "none"
166167
reportExplicitAny = "none"
167168
reportIgnoreCommentWithoutRule = "none"
169+
reportImplicitAbstractClass = "none"
168170
reportImplicitOverride = "none"
169171
reportImplicitStringConcatenation = "none"
170172
reportImportCycles = "none"
@@ -184,11 +186,6 @@ exclude = [
184186
"temporalio/bridge/proto",
185187
"tests/worker/workflow_sandbox/testmodules/proto",
186188
"temporalio/bridge/worker.py",
187-
"temporalio/contrib/opentelemetry.py",
188-
"temporalio/contrib/pydantic.py",
189-
"temporalio/converter.py",
190-
"temporalio/testing/_workflow.py",
191-
"temporalio/worker/_activity.py",
192189
"temporalio/worker/_replayer.py",
193190
"temporalio/worker/_worker.py",
194191
"temporalio/worker/workflow_sandbox/_importer.py",
@@ -203,9 +200,7 @@ exclude = [
203200
"tests/contrib/pydantic/workflows.py",
204201
"tests/test_converter.py",
205202
"tests/test_service.py",
206-
"tests/test_workflow.py",
207203
"tests/worker/test_activity.py",
208-
"tests/worker/test_workflow.py",
209204
"tests/worker/workflow_sandbox/test_importer.py",
210205
"tests/worker/workflow_sandbox/test_restrictions.py",
211206
# TODO: these pass locally but fail in CI with
@@ -236,6 +231,3 @@ exclude = [
236231
[tool.uv]
237232
# Prevent uv commands from building the package by default
238233
package = false
239-
240-
[tool.uv.sources]
241-
nexus-rpc = { git = "https://github.com/nexus-rpc/sdk-python.git", rev = "35f574c711193a6e2560d3e6665732a5bb7ae92c" }

0 commit comments

Comments
 (0)