Skip to content

Commit 29bcbb7

Browse files
add alternater_name decorator
Signed-off-by: Mukundan Sundararajan <65565396+mukundansundar@users.noreply.github.com>
1 parent 2688f3c commit 29bcbb7

File tree

5 files changed

+132
-42
lines changed

5 files changed

+132
-42
lines changed

ext/dapr-ext-workflow/dapr/ext/workflow/__init__.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414
"""
1515

1616
# Import your main classes here
17-
from dapr.ext.workflow.workflow_runtime import WorkflowRuntime
17+
from dapr.ext.workflow.workflow_runtime import WorkflowRuntime, alternate_name
1818
from dapr.ext.workflow.dapr_workflow_client import DaprWorkflowClient
1919
from dapr.ext.workflow.dapr_workflow_context import DaprWorkflowContext, when_all, when_any
2020
from dapr.ext.workflow.workflow_activity_context import WorkflowActivityContext
@@ -28,5 +28,6 @@
2828
'WorkflowState',
2929
'WorkflowStatus',
3030
'when_all',
31-
'when_any'
31+
'when_any',
32+
'alternate_name'
3233
]

ext/dapr-ext-workflow/dapr/ext/workflow/dapr_workflow_client.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -75,8 +75,8 @@ def schedule_new_workflow(self, workflow: Workflow, *, input: Optional[TInput] =
7575
Returns:
7676
The ID of the scheduled workflow instance.
7777
"""
78-
if hasattr(workflow, '_registered_name'):
79-
return self.__obj.schedule_new_orchestration(workflow.__dict__['_registered_name'],
78+
if hasattr(workflow, '_alternate_name'):
79+
return self.__obj.schedule_new_orchestration(workflow.__dict__['_alternate_name'],
8080
input=input, instance_id=instance_id,
8181
start_at=start_at)
8282
return self.__obj.schedule_new_orchestration(workflow.__name__, input=input,

ext/dapr-ext-workflow/dapr/ext/workflow/dapr_workflow_context.py

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -53,9 +53,10 @@ def create_timer(self, fire_at: Union[datetime, timedelta]) -> task.Task:
5353

5454
def call_activity(self, activity: Callable[[WorkflowActivityContext, TInput], TOutput], *,
5555
input: TInput = None) -> task.Task[TOutput]:
56-
if hasattr(activity, '_registered_name'):
57-
return self.__obj.call_activity(activity=activity.__dict__['_registered_name'],
56+
if hasattr(activity, '_alternate_name'):
57+
return self.__obj.call_activity(activity=activity.__dict__['_alternate_name'],
5858
input=input)
59+
# this return should ideally never execute
5960
return self.__obj.call_activity(activity=activity.__name__, input=input)
6061

6162
def call_child_workflow(self, workflow: Workflow, *,
@@ -66,10 +67,10 @@ def wf(ctx: task.OrchestrationContext, inp: TInput):
6667
return workflow(daprWfContext, inp)
6768
# copy workflow name so durabletask.worker can find the orchestrator in its registry
6869

69-
# Any workflow function using python decorator will have a _registered_name attribute
70-
if hasattr(workflow, '_registered_name'):
71-
wf.__name__ = workflow.__dict__['_registered_name']
70+
if hasattr(workflow, '_alternate_name'):
71+
wf.__name__ = workflow.__dict__['_alternate_name']
7272
else:
73+
# this case should ideally never happen
7374
wf.__name__ = workflow.__name__
7475
return self.__obj.call_sub_orchestrator(wf, input=input, instance_id=instance_id)
7576

ext/dapr-ext-workflow/dapr/ext/workflow/workflow_runtime.py

Lines changed: 78 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -60,14 +60,20 @@ def orchestrationWrapper(ctx: task.OrchestrationContext, inp: Optional[TInput] =
6060
return fn(daprWfContext)
6161
return fn(daprWfContext, inp)
6262

63-
if hasattr(fn, '_registered_name'):
64-
raise ValueError(f'Workflow {fn.__name__} already registered as {fn._registered_name}')
65-
fn.__dict__['_registered_name'] = name if name else fn.__name__
66-
67-
if name:
68-
self.__worker._registry.add_named_orchestrator(name, orchestrationWrapper)
69-
return
70-
self.__worker._registry.add_named_orchestrator(fn.__name__, orchestrationWrapper)
63+
if hasattr(fn, '_workflow_registered'):
64+
# whenever a workflow is registered, it also has _alternate_name attribute
65+
alt_name = fn.__dict__['_alternate_name']
66+
raise ValueError(f'Workflow {fn.__name__} already registered as {alt_name}')
67+
if hasattr(fn, '_alternate_name'):
68+
if name is not None:
69+
m = f'Workflow {fn.__name__} already has an alternate name {fn._alternate_name}'
70+
raise ValueError(m)
71+
else:
72+
fn.__dict__['_alternate_name'] = name if name else fn.__name__
73+
74+
self.__worker._registry.add_named_orchestrator(fn.__dict__['_alternate_name'],
75+
orchestrationWrapper)
76+
fn.__dict__['_workflow_registered'] = True
7177

7278
def register_activity(self, fn: Activity, *, name: Optional[str] = None):
7379
"""Registers a workflow activity as a function that takes
@@ -80,14 +86,20 @@ def activityWrapper(ctx: task.ActivityContext, inp: Optional[TInput] = None):
8086
return fn(wfActivityContext)
8187
return fn(wfActivityContext, inp)
8288

83-
if hasattr(fn, '_registered_name'):
84-
raise ValueError(f'Activity {fn.__name__} already registered as {fn._registered_name}')
85-
fn.__dict__['_registered_name'] = name if name else fn.__name__
89+
if hasattr(fn, '_activity_registered'):
90+
# whenever an activity is registered, it also has _alternate_name attribute
91+
alt_name = fn.__dict__['_alternate_name']
92+
raise ValueError(f'Activity {fn.__name__} already registered as {alt_name}')
93+
if hasattr(fn, '_alternate_name'):
94+
if name is not None:
95+
m = f'Activity {fn.__name__} already has an alternate name {fn._alternate_name}'
96+
raise ValueError(m)
97+
else:
98+
fn.__dict__['_alternate_name'] = name if name else fn.__name__
99+
86100

87-
if name:
88-
self.__worker._registry.add_named_activity(name, activityWrapper)
89-
return
90-
self.__worker._registry.add_named_activity(fn.__name__, activityWrapper)
101+
self.__worker._registry.add_named_activity(fn.__dict__['_alternate_name'], activityWrapper)
102+
fn.__dict__['_activity_registered'] = True
91103

92104
def start(self):
93105
"""Starts the listening for work items on a background thread."""
@@ -129,8 +141,10 @@ def wrapper(fn: Workflow):
129141
@wraps(fn)
130142
def innerfn():
131143
return fn
132-
133-
innerfn.__dict__['_registered_name'] = name if name else fn.__name__
144+
if hasattr(fn, '_alternate_name'):
145+
innerfn.__dict__['_alternate_name'] = fn.__dict__['_alternate_name']
146+
else:
147+
innerfn.__dict__['_alternate_name'] = name if name else fn.__name__
134148
innerfn.__signature__ = inspect.signature(fn)
135149
return innerfn
136150

@@ -174,7 +188,10 @@ def wrapper(fn: Activity):
174188
def innerfn():
175189
return fn
176190

177-
innerfn.__dict__['_registered_name'] = name if name else fn.__name__
191+
if hasattr(fn, '_alternate_name'):
192+
innerfn.__dict__['_alternate_name'] = fn.__dict__['_alternate_name']
193+
else:
194+
innerfn.__dict__['_alternate_name'] = name if name else fn.__name__
178195
innerfn.__signature__ = inspect.signature(fn)
179196
return innerfn
180197

@@ -184,3 +201,46 @@ def innerfn():
184201
return wrapper(__fn)
185202

186203
return wrapper
204+
205+
def alternate_name(name: Optional[str] = None):
206+
"""Decorator to register a workflow or activity function with an alternate name.
207+
208+
This example shows how to register a workflow function with a name:
209+
210+
from dapr.ext.workflow import WorkflowRuntime
211+
wfr = WorkflowRuntime()
212+
213+
@wfr.workflow
214+
@alternate_name(add")
215+
def add(ctx, x: int, y: int) -> int:
216+
return x + y
217+
218+
This example shows how to register an activity function with a name:
219+
220+
from dapr.ext.workflow import WorkflowRuntime
221+
wfr = WorkflowRuntime()
222+
223+
@wfr.activity
224+
@alternate_name("add")
225+
def add(ctx, x: int, y: int) -> int:
226+
return x + y
227+
228+
Args:
229+
name (Optional[str], optional): Name to identify the workflow or activity function as in
230+
the workflow runtime. Defaults to None.
231+
"""
232+
233+
def wrapper(fn: any):
234+
if hasattr(fn, '_alternate_name'):
235+
raise ValueError(f'Function {fn.__name__} already has an alternate name {fn._alternate_name}')
236+
fn.__dict__['_alternate_name'] = name if name else fn.__name__
237+
238+
@wraps(fn)
239+
def innerfn(*args, **kwargs):
240+
return fn(*args, **kwargs)
241+
242+
innerfn.__dict__['_alternate_name'] = name if name else fn.__name__
243+
innerfn.__signature__ = inspect.signature(fn)
244+
return innerfn
245+
246+
return wrapper

ext/dapr-ext-workflow/tests/test_workflow_runtime.py

Lines changed: 43 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -15,9 +15,11 @@
1515

1616
from typing import List
1717
import unittest
18+
import logging
19+
import inspect
1820
from dapr.ext.workflow.dapr_workflow_context import DaprWorkflowContext
1921
from unittest import mock
20-
from dapr.ext.workflow.workflow_runtime import WorkflowRuntime
22+
from dapr.ext.workflow.workflow_runtime import WorkflowRuntime, alternate_name
2123
from dapr.ext.workflow.workflow_activity_context import WorkflowActivityContext
2224

2325
listOrchestrators: List[str] = []
@@ -35,14 +37,20 @@ def add_named_activity(self, name: str, fn):
3537
class WorkflowRuntimeTest(unittest.TestCase):
3638

3739
def setUp(self):
40+
self.log = logging.getLogger(__name__)
3841
listActivities.clear()
3942
listOrchestrators.clear()
4043
mock.patch('durabletask.worker._Registry', return_value=FakeTaskHubGrpcWorker()).start()
4144
self.runtime_options = WorkflowRuntime()
42-
if hasattr(self.mock_client_wf, "_registered_name"):
43-
del self.mock_client_wf.__dict__["_registered_name"]
44-
if hasattr(self.mock_client_activity, "_registered_name"):
45-
del self.mock_client_activity.__dict__["_registered_name"]
45+
if hasattr(self.mock_client_wf, "_alternate_name"):
46+
del self.mock_client_wf.__dict__["_alternate_name"]
47+
if hasattr(self.mock_client_activity, "_alternate_name"):
48+
del self.mock_client_activity.__dict__["_alternate_name"]
49+
if hasattr(self.mock_client_wf, "_workflow_registered"):
50+
del self.mock_client_wf.__dict__["_workflow_registered"]
51+
if hasattr(self.mock_client_activity, "_activity_registered"):
52+
del self.mock_client_activity.__dict__["_activity_registered"]
53+
4654

4755
def mock_client_wf(ctx: DaprWorkflowContext, input):
4856
print(f'{input}')
@@ -54,39 +62,46 @@ def test_register(self):
5462
self.runtime_options.register_workflow(self.mock_client_wf, name="mock_client_wf")
5563
wanted_orchestrator = [self.mock_client_wf.__name__]
5664
assert listOrchestrators == wanted_orchestrator
65+
assert self.mock_client_wf._alternate_name == "mock_client_wf"
66+
assert self.mock_client_wf._workflow_registered
5767

5868
self.runtime_options.register_activity(self.mock_client_activity)
5969
wanted_activity = [self.mock_client_activity.__name__]
6070
assert listActivities == wanted_activity
71+
assert self.mock_client_activity._activity_registered
6172

6273
def test_decorator_register(self):
6374
client_wf = (self.runtime_options.workflow())(self.mock_client_wf)
6475
wanted_orchestrator = [self.mock_client_wf.__name__]
6576
assert listOrchestrators == wanted_orchestrator
66-
assert client_wf._registered_name == self.mock_client_wf.__name__
77+
assert client_wf._alternate_name == self.mock_client_wf.__name__
78+
assert self.mock_client_wf._workflow_registered
6779

6880
client_activity = (self.runtime_options.activity())(self.mock_client_activity)
6981
wanted_activity = [self.mock_client_activity.__name__]
7082
assert listActivities == wanted_activity
71-
assert client_activity._registered_name == self.mock_client_activity.__name__
83+
assert client_activity._alternate_name == self.mock_client_activity.__name__
84+
assert self.mock_client_activity._activity_registered
7285

7386
def test_both_decorator_and_register(self):
7487
client_wf = (self.runtime_options.workflow(name="test_wf"))(self.mock_client_wf)
7588
wanted_orchestrator = ["test_wf"]
7689
assert listOrchestrators == wanted_orchestrator
77-
assert client_wf._registered_name == "test_wf"
90+
assert client_wf._alternate_name == "test_wf"
91+
assert self.mock_client_wf._workflow_registered
7892

7993
self.runtime_options.register_activity(self.mock_client_activity, name="test_act")
8094
wanted_activity = ["test_act"]
8195
assert listActivities == wanted_activity
82-
assert hasattr(self.mock_client_activity, "_registered_name")
96+
assert hasattr(self.mock_client_activity, "_alternate_name")
97+
assert self.mock_client_activity._activity_registered
8398

8499
def test_register_wf_act_using_both_decorator_and_method(self):
85100
client_wf = (self.runtime_options.workflow(name="test_wf"))(self.mock_client_wf)
86101

87102
wanted_orchestrator = ["test_wf"]
88103
assert listOrchestrators == wanted_orchestrator
89-
assert client_wf._registered_name == "test_wf"
104+
assert client_wf._alternate_name == "test_wf"
90105
with self.assertRaises(ValueError) as exeception_context:
91106
self.runtime_options.register_workflow(self.mock_client_wf)
92107
wf_name = self.mock_client_wf.__name__
@@ -96,19 +111,32 @@ def test_register_wf_act_using_both_decorator_and_method(self):
96111
client_act = (self.runtime_options.activity(name="test_act"))(self.mock_client_activity)
97112
wanted_activity = ["test_act"]
98113
assert listActivities == wanted_activity
99-
assert client_act._registered_name == "test_act"
114+
assert client_act._alternate_name == "test_act"
100115
with self.assertRaises(ValueError) as exeception_context:
101116
self.runtime_options.register_activity(self.mock_client_activity)
102117
act_name = self.mock_client_activity.__name__
103118
self.assertEqual(exeception_context.exception.args[0],
104119
f'Activity {act_name} already registered as test_act')
105120

121+
def test_duplicate_alternate_name_registration(self):
122+
client_wf = (alternate_name(name="test"))(self.mock_client_wf)
123+
with self.assertRaises(ValueError) as exeception_context:
124+
(self.runtime_options.workflow(name="random"))(client_wf)
125+
self.assertEqual(exeception_context.exception.args[0],
126+
f'Workflow {client_wf.__name__} already has an alternate name test')
127+
128+
client_act = (alternate_name(name="test"))(self.mock_client_activity)
129+
with self.assertRaises(ValueError) as exeception_context:
130+
(self.runtime_options.activity(name="random"))(client_act)
131+
self.assertEqual(exeception_context.exception.args[0],
132+
f'Activity {client_act.__name__} already has an alternate name test')
133+
106134
def test_register_wf_act_using_both_decorator_and_method_without_name(self):
107135
client_wf = (self.runtime_options.workflow())(self.mock_client_wf)
108136

109137
wanted_orchestrator = ["mock_client_wf"]
110138
assert listOrchestrators == wanted_orchestrator
111-
assert client_wf._registered_name == "mock_client_wf"
139+
assert client_wf._alternate_name == "mock_client_wf"
112140
with self.assertRaises(ValueError) as exeception_context:
113141
self.runtime_options.register_workflow(self.mock_client_wf, name="test_wf")
114142
wf_name = self.mock_client_wf.__name__
@@ -118,7 +146,7 @@ def test_register_wf_act_using_both_decorator_and_method_without_name(self):
118146
client_act = (self.runtime_options.activity())(self.mock_client_activity)
119147
wanted_activity = ["mock_client_activity"]
120148
assert listActivities == wanted_activity
121-
assert client_act._registered_name == "mock_client_activity"
149+
assert client_act._alternate_name == "mock_client_activity"
122150
with self.assertRaises(ValueError) as exeception_context:
123151
self.runtime_options.register_activity(self.mock_client_activity, name="test_act")
124152
act_name = self.mock_client_activity.__name__
@@ -129,9 +157,9 @@ def test_decorator_register_optinal_name(self):
129157
client_wf = (self.runtime_options.workflow(name="test_wf"))(self.mock_client_wf)
130158
wanted_orchestrator = ["test_wf"]
131159
assert listOrchestrators == wanted_orchestrator
132-
assert client_wf._registered_name == "test_wf"
160+
assert client_wf._alternate_name == "test_wf"
133161

134162
client_act = (self.runtime_options.activity(name="test_act"))(self.mock_client_activity)
135163
wanted_activity = ["test_act"]
136164
assert listActivities == wanted_activity
137-
assert client_act._registered_name == "test_act"
165+
assert client_act._alternate_name == "test_act"

0 commit comments

Comments
 (0)