Skip to content

Commit 8a93453

Browse files
authored
Call SubOrchestrator by Function Name (#437)
* call suborcheatrstor by name * remove space * remove blank line * Update DurableOrchestrationContext.py
1 parent 021e9ee commit 8a93453

File tree

2 files changed

+65
-4
lines changed

2 files changed

+65
-4
lines changed

azure/durable_functions/models/DurableOrchestrationContext.py

Lines changed: 26 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -247,13 +247,13 @@ def call_http(self, method: str, uri: str, content: Optional[str] = None,
247247
return task
248248

249249
def call_sub_orchestrator(self,
250-
name: str, input_: Optional[Any] = None,
250+
name: Union[str, Callable], input_: Optional[Any] = None,
251251
instance_id: Optional[str] = None) -> TaskBase:
252252
"""Schedule sub-orchestration function named `name` for execution.
253253
254254
Parameters
255255
----------
256-
name: str
256+
name: Union[str, Callable]
257257
The name of the orchestrator function to call.
258258
input_: Optional[Any]
259259
The JSON-serializable input to pass to the orchestrator function.
@@ -265,19 +265,30 @@ def call_sub_orchestrator(self,
265265
Task
266266
A Durable Task that completes when the called sub-orchestrator completes or fails.
267267
"""
268+
if isinstance(name, Callable) and not isinstance(name, FunctionBuilder):
269+
error_message = "The `call_activity` API received a `Callable` without an "\
270+
"associated Azure Functions trigger-type. "\
271+
"Please ensure you're using the Python programming model V2 "\
272+
"and that your activity function is annotated with the `activity_trigger`"\
273+
"decorator. Otherwise, provide in the name of the activity as a string."
274+
raise ValueError(error_message)
275+
276+
if isinstance(name, FunctionBuilder):
277+
name = self._get_function_name(name, OrchestrationTrigger)
278+
268279
action = CallSubOrchestratorAction(name, input_, instance_id)
269280
task = self._generate_task(action)
270281
return task
271282

272283
def call_sub_orchestrator_with_retry(self,
273-
name: str, retry_options: RetryOptions,
284+
name: Union[str, Callable], retry_options: RetryOptions,
274285
input_: Optional[Any] = None,
275286
instance_id: Optional[str] = None) -> TaskBase:
276287
"""Schedule sub-orchestration function named `name` for execution, with retry-options.
277288
278289
Parameters
279290
----------
280-
name: str
291+
name: Union[str, Callable]
281292
The name of the activity function to schedule.
282293
retry_options: RetryOptions
283294
The settings for retrying this sub-orchestrator in case of a failure.
@@ -291,6 +302,17 @@ def call_sub_orchestrator_with_retry(self,
291302
Task
292303
A Durable Task that completes when the called sub-orchestrator completes or fails.
293304
"""
305+
if isinstance(name, Callable) and not isinstance(name, FunctionBuilder):
306+
error_message = "The `call_activity` API received a `Callable` without an "\
307+
"associated Azure Functions trigger-type. "\
308+
"Please ensure you're using the Python programming model V2 "\
309+
"and that your activity function is annotated with the `activity_trigger`"\
310+
"decorator. Otherwise, provide in the name of the activity as a string."
311+
raise ValueError(error_message)
312+
313+
if isinstance(name, FunctionBuilder):
314+
name = self._get_function_name(name, OrchestrationTrigger)
315+
294316
action = CallSubOrchestratorWithRetryAction(name, retry_options, input_, instance_id)
295317
task = self._generate_task(action, retry_options)
296318
return task

tests/orchestrator/test_sub_orchestrator.py

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,10 @@
55
from azure.durable_functions.models.OrchestratorState import OrchestratorState
66
from azure.durable_functions.models.actions.CallSubOrchestratorAction \
77
import CallSubOrchestratorAction
8+
import azure.durable_functions as df
9+
import azure.functions as func
810

11+
app = df.DFApp(http_auth_level=func.AuthLevel.ANONYMOUS)
912

1013
def generator_function(context):
1114
outputs = []
@@ -19,6 +22,22 @@ def generator_function(context):
1922

2023
return outputs
2124

25+
def generator_function_call_by_function_name(context):
26+
outputs = []
27+
task1 = yield context.call_sub_orchestrator(HelloSubOrchestrator, "Tokyo")
28+
task2 = yield context.call_sub_orchestrator(HelloSubOrchestrator, "Seattle")
29+
task3 = yield context.call_sub_orchestrator(HelloSubOrchestrator, "London")
30+
31+
outputs.append(task1)
32+
outputs.append(task2)
33+
outputs.append(task3)
34+
35+
return outputs
36+
37+
@app.orchestration_trigger(context_name="context")
38+
def HelloSubOrchestrator(context):
39+
return "Hello" + context
40+
2241
def base_expected_state(output=None, replay_schema: ReplaySchema = ReplaySchema.V1) -> OrchestratorState:
2342
return OrchestratorState(is_done=False, actions=[], output=output, replay_schema=replay_schema.value)
2443

@@ -54,3 +73,23 @@ def test_tokyo_and_seattle_and_london_state():
5473

5574
#assert_valid_schema(result)
5675
assert_orchestration_state_equals(expected, result)
76+
77+
def test_call_suborchestrator_by_name():
78+
context_builder = ContextBuilder('test_call_suborchestrator_by_name')
79+
add_hello_suborch_completed_events(context_builder, 0, "\"Hello Tokyo!\"")
80+
add_hello_suborch_completed_events(context_builder, 1, "\"Hello Seattle!\"")
81+
add_hello_suborch_completed_events(context_builder, 2, "\"Hello London!\"")
82+
83+
result = get_orchestration_state_result(
84+
context_builder, generator_function_call_by_function_name)
85+
86+
expected_state = base_expected_state(
87+
['Hello Tokyo!', 'Hello Seattle!', 'Hello London!'])
88+
add_hello_suborch_action(expected_state, 'Tokyo')
89+
add_hello_suborch_action(expected_state, 'Seattle')
90+
add_hello_suborch_action(expected_state, 'London')
91+
expected_state._is_done = True
92+
expected = expected_state.to_json()
93+
94+
#assert_valid_schema(result)
95+
assert_orchestration_state_equals(expected, result)

0 commit comments

Comments
 (0)