Skip to content

Commit 7788392

Browse files
Make the before-and-after nature of interceptors clear (#4308)
* Make the before-and-after nature of interceptors clear * Clarify distinction between inbound and outbound interceptors * Apply suggestions from code review Co-authored-by: Lenny Chen <55669665+lennessyy@users.noreply.github.com> --------- Co-authored-by: Lenny Chen <55669665+lennessyy@users.noreply.github.com>
1 parent 2ae4147 commit 7788392

2 files changed

Lines changed: 51 additions & 26 deletions

File tree

docs/develop/python/interceptors.mdx

Lines changed: 40 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -15,15 +15,18 @@ tags:
1515
---
1616

1717
Interceptors are SDK hooks that let you intercept inbound and outbound Temporal calls. You use them to add common
18-
behavior across many calls, such as tracing and context propagation, before calls reach the SDK's underlying
19-
implementation. This is similar to using middleware in web frameworks such as
18+
behavior across many calls, such as tracing and context propagation.
19+
This is similar to using middleware in web frameworks such as
2020
[Django](https://docs.djangoproject.com/en/5.2/topics/http/middleware/),
2121
[Starlette](https://www.starlette.io/middleware/), and
2222
[Flask](https://flask.palletsprojects.com/en/stable/lifecycle/#middleware).
2323

24-
The methods you implement on your Interceptor classes can perform side effects and modify incoming or outgoing data.
24+
There are two types of interceptors--inbound and outbound.
2525

26-
There are five categories of inbound and outbound calls that you can modify in this way:
26+
* Outbound interceptors wrap network calls, running before they reach the network and after they return.
27+
* Inbound interceptors run after the network hop, wrapping application code and running before it starts and after it returns.
28+
29+
Concretely, there are five categories of inbound and outbound calls that you can modify in this way:
2730

2831
| [Outbound Client calls](https://python.temporal.io/temporalio.client.OutboundInterceptor.html) | [Inbound Workflow calls](https://python.temporal.io/temporalio.worker.WorkflowInboundInterceptor.html) | [Outbound Workflow calls](https://python.temporal.io/temporalio.worker.WorkflowOutboundInterceptor.html) | [Inbound Activity calls](https://python.temporal.io/temporalio.worker.ActivityInboundInterceptor.html) | [Outbound Activity calls](https://python.temporal.io/temporalio.worker.ActivityOutboundInterceptor.html) |
2932
| ---------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------ | ---------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------ | -------------------------------------------------------------------------------------------------------- |
@@ -83,7 +86,12 @@ automatically.
8386

8487
:::
8588

86-
## Client call Interceptors
89+
## How to implement Interceptors in Python
90+
91+
Interceptors run as a chain. Each interceptor wraps the entire inner call: your code runs before the call, invokes `next` to execute the rest of the chain, and then runs after the call completes. This means you can inspect or modify both the `input` and the result, handle errors, and perform side effects at either stage.
92+
93+
94+
### Implementing Client call Interceptors
8795

8896
To modify outbound Client calls, define a class inheriting from
8997
[`client.Interceptor`](https://python.temporal.io/temporalio.client.Interceptor.html), and implement the method
@@ -151,17 +159,19 @@ look to the first Interceptor instance, get hold of the appropriate intercepted
151159
method will perform its function then call the same method on the next Interceptor in the chain. At the end of the chain
152160
the SDK will call the "real" SDK method.
153161

154-
## Worker call Interceptors
162+
### Implementing Worker call Interceptors
155163

156164
To modify inbound and outbound Workflow and Activity calls, define a class inheriting from `worker.Interceptor`. This is
157165
an interface with two methods named `intercept_activity` and `workflow_interceptor_class`, which you can use to
158166
configure interceptions of Activity and Workflow calls, respectively. `intercept_activity` returns an
159167
`ActivityInboundInterceptor`.
160168

161-
This example demonstrates using an interceptor to measure
162-
[Schedule-To-Start](/encyclopedia/detecting-activity-failures#schedule-to-start-timeout) latency:
169+
This example demonstrates using an interceptor to measure [Schedule-To-Start](/encyclopedia/detecting-activity-failures#schedule-to-start-timeout) and Schedule-To-Close latency.
170+
Notice how the interceptor wraps the call: it records Schedule-To-Start before `execute_activity`, then records Schedule-To-Close after it completes:
163171

164172
```python
173+
from datetime import datetime, timezone
174+
from temporalio import activity
165175
from temporalio.worker import (
166176
ActivityInboundInterceptor,
167177
ExecuteActivityInput,
@@ -173,27 +183,35 @@ class SimpleWorkerInterceptor(Interceptor):
173183
def intercept_activity(
174184
self, next: ActivityInboundInterceptor
175185
) -> ActivityInboundInterceptor:
176-
return CustomScheduleToStartInterceptor(next)
186+
return ActivityMetricsInterceptor(next)
177187

178188

179-
class CustomScheduleToStartInterceptor(ActivityInboundInterceptor):
189+
class ActivityMetricsInterceptor(ActivityInboundInterceptor):
180190
async def execute_activity(self, input: ExecuteActivityInput):
181-
182-
schedule_to_start = (
183-
activity.info().started_time
184-
- activity.info().current_attempt_scheduled_time
185-
)
186-
191+
info = activity.info()
187192
meter = activity.metric_meter()
188-
histogram = meter.create_histogram_timedelta(
193+
attrs = {"workflow_type": info.workflow_type}
194+
195+
# Before the activity executes: record Schedule-To-Start
196+
schedule_to_start = info.started_time - info.current_attempt_scheduled_time
197+
meter.create_histogram_timedelta(
189198
"custom_activity_schedule_to_start_latency",
190199
description="Time between activity scheduling and start",
191200
unit="duration",
192-
)
193-
histogram.record(
194-
schedule_to_start, {"workflow_type": activity.info().workflow_type}
195-
)
196-
return await self.next.execute_activity(input)
201+
).record(schedule_to_start, attrs)
202+
203+
# Execute the activity
204+
result = await self.next.execute_activity(input)
205+
206+
# After the activity completes: record Schedule-To-Close
207+
elapsed = datetime.now(timezone.utc) - info.current_attempt_scheduled_time
208+
meter.create_histogram_timedelta(
209+
"custom_activity_schedule_to_close_latency",
210+
description="Time between activity scheduling and completion",
211+
unit="duration",
212+
).record(elapsed, attrs)
213+
214+
return result
197215

198216
client = await Client.connect(
199217
"localhost:7233",

docs/develop/typescript/interceptors.mdx

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -15,11 +15,20 @@ description:
1515
---
1616

1717
Interceptors are SDK hooks that let you intercept inbound and outbound Temporal calls. You use them to apply shared
18-
behavior across many calls, such as tracing and authorization, before calls reach the SDK's underlying implementation.
18+
behavior across many calls, such as tracing and authorization, before calls reach the application code and after they return.
1919
This is similar to middleware in other frameworks.
2020

21+
There are two main types of interceptors--inbound and outbound.
22+
23+
* Outbound interceptors wrap network calls, running before they reach the network and after they return.
24+
* Inbound interceptors run after the network hop, wrapping application code and running before it starts and after it returns.
25+
26+
Those further break down into concrete Interceptor types--see below.
27+
2128
## How to implement interceptors in TypeScript {#interceptors}
2229

30+
Interceptors run as a chain. Each interceptor wraps the entire inner call: your code runs before the call, invokes `next` to execute the rest of the chain, and then runs after the call completes. This means you can inspect or modify both the `input` and the result, handle errors, and perform side effects at either stage.
31+
2332
The TypeScript SDK comes with an optional interceptor package that adds tracing with
2433
[OpenTelemetry](https://www.npmjs.com/package/@temporalio/interceptors-opentelemetry). See how to use it in the
2534
[interceptors-opentelemetry](https://github.com/temporalio/samples-typescript/tree/main/interceptors-opentelemetry) code
@@ -36,9 +45,7 @@ sample.
3645
[`WorkflowHandle`](https://typescript.temporal.io/api/interfaces/client.WorkflowHandle) like starting or signaling a
3746
Workflow.
3847

39-
Interceptors are run in a chain, and all interceptors work similarly. They accept two arguments: `input` and `next`,
40-
where `next` calls the next interceptor in the chain. All interceptor methods are optional—it's up to the implementor to
41-
choose which methods to intercept.
48+
All interceptor methods are optional—it's up to the implementor to choose which methods to intercept.
4249

4350
## Interceptor examples
4451

0 commit comments

Comments
 (0)