From f3e79ec1c879c34d8bb8c758703ee02fec30e848 Mon Sep 17 00:00:00 2001 From: Andy Staples Date: Fri, 9 May 2025 15:23:30 -0600 Subject: [PATCH 1/5] Expose OrchestratorGeneratorWrapper in SDK --- azure-functions-durable-python.sln | 29 ++++++++++++ azure/durable_functions/models/__init__.py | 4 +- .../testing/OrchestratorGeneratorWrapper.py | 45 ++++++++++++++++++ azure/durable_functions/testing/__init__.py | 6 +++ .../blueprint/tests/test_my_orchestrator.py | 40 ++++------------ .../tests/test_E2_BackupSiteContent.py | 47 ++++++------------- .../tests/test_my_orchestrator.py | 39 ++++----------- 7 files changed, 116 insertions(+), 94 deletions(-) create mode 100644 azure-functions-durable-python.sln create mode 100644 azure/durable_functions/testing/OrchestratorGeneratorWrapper.py create mode 100644 azure/durable_functions/testing/__init__.py diff --git a/azure-functions-durable-python.sln b/azure-functions-durable-python.sln new file mode 100644 index 00000000..81511d0c --- /dev/null +++ b/azure-functions-durable-python.sln @@ -0,0 +1,29 @@ +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 17 +VisualStudioVersion = 17.5.2.0 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "samples", "samples", "{5D20AA90-6969-D8BD-9DCD-8634F4692FDA}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "extensions", "samples\aml_monitoring\extensions.csproj", "{33E598B8-4178-679F-9B92-BE8D8A64F1A5}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {33E598B8-4178-679F-9B92-BE8D8A64F1A5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {33E598B8-4178-679F-9B92-BE8D8A64F1A5}.Debug|Any CPU.Build.0 = Debug|Any CPU + {33E598B8-4178-679F-9B92-BE8D8A64F1A5}.Release|Any CPU.ActiveCfg = Release|Any CPU + {33E598B8-4178-679F-9B92-BE8D8A64F1A5}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(NestedProjects) = preSolution + {33E598B8-4178-679F-9B92-BE8D8A64F1A5} = {5D20AA90-6969-D8BD-9DCD-8634F4692FDA} + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {7D002670-7479-46AF-99FF-1EBAB72B6D22} + EndGlobalSection +EndGlobal diff --git a/azure/durable_functions/models/__init__.py b/azure/durable_functions/models/__init__.py index a61511d2..7737e9ae 100644 --- a/azure/durable_functions/models/__init__.py +++ b/azure/durable_functions/models/__init__.py @@ -9,6 +9,7 @@ from .DurableHttpRequest import DurableHttpRequest from .TokenSource import ManagedIdentityTokenSource from .DurableEntityContext import DurableEntityContext +from .Task import TaskBase __all__ = [ 'DurableOrchestrationBindings', @@ -20,5 +21,6 @@ 'OrchestratorState', 'OrchestrationRuntimeStatus', 'PurgeHistoryResult', - 'RetryOptions' + 'RetryOptions', + 'TaskBase' ] diff --git a/azure/durable_functions/testing/OrchestratorGeneratorWrapper.py b/azure/durable_functions/testing/OrchestratorGeneratorWrapper.py new file mode 100644 index 00000000..254482ec --- /dev/null +++ b/azure/durable_functions/testing/OrchestratorGeneratorWrapper.py @@ -0,0 +1,45 @@ +from typing import Generator, Any + +from azure.durable_functions.models import TaskBase +class OrchestratorGeneratorWrapper: + """A helper class for unit testing orchestrator functions. + + Defines a method that will unwrap a user orchestrator function in a way that is similar to the + Durable replay logic, but without saving state for the Durable Task Hub. + """ + + @staticmethod + def wrap(generator: Generator[TaskBase, Any, Any]) -> Generator[TaskBase, None, Any]: + """Wraps the generator to simulate the Durable replay logic. + + Parameters + ---------- + generator: Generator[TaskBase, Any, Any] + Generator orchestrator as defined in the user function app. This generator is expected + to yield a series of TaskBase objects and recieve the results of these tasks until + returning the result of the orchestrator. + + Returns + ------- + Generator[TaskBase, None, Any] + A simplified version of the orchestrator which takes no inputs. This generator will + yield back the TaskBase objects that are yielded from the user orchestrator as well + as the final result of the orchestrator. Exception handling is also simulated here + in the same way as replay, where tasks returning exceptions are thrown back into the + orchestrator. + """ + previous = next(generator) + yield previous + while True: + try: + previous_result = None + try: + previous_result = previous.result + except Exception as e: # Simulated activity exceptions, timer interrupted exceptions, anytime a task would throw. + previous = generator.throw(e) + else: + previous = generator.send(previous_result) + yield previous + except StopIteration as e: + yield e.value + return \ No newline at end of file diff --git a/azure/durable_functions/testing/__init__.py b/azure/durable_functions/testing/__init__.py new file mode 100644 index 00000000..a7bd4cfe --- /dev/null +++ b/azure/durable_functions/testing/__init__.py @@ -0,0 +1,6 @@ +"""Unit testing utilities for Azure Durable functions.""" +from .OrchestratorGeneratorWrapper import OrchestratorGeneratorWrapper + +__all__ = [ + 'OrchestratorGeneratorWrapper' +] diff --git a/samples-v2/blueprint/tests/test_my_orchestrator.py b/samples-v2/blueprint/tests/test_my_orchestrator.py index b27969dc..60d8b977 100644 --- a/samples-v2/blueprint/tests/test_my_orchestrator.py +++ b/samples-v2/blueprint/tests/test_my_orchestrator.py @@ -1,44 +1,21 @@ from datetime import timedelta import unittest from unittest.mock import Mock, call, patch +from azure.durable_functions.testing import OrchestratorGeneratorWrapper from durable_blueprints import my_orchestrator -# A way to wrap an orchestrator generator to simplify calling it and getting the results. -# Because orchestrators in Durable Functions always accept the result of the previous activity for the next send() call, -# we can unwrap the orchestrator generator using this method to simplify per-test code. -def orchestrator_generator_wrapper(generator): - previous = next(generator) - yield previous - while True: - try: - previous_result = None - try: - previous_result = previous.result - except Exception as e: # Simulated activity exceptions, timer interrupted exceptions, anytime a task would throw. - previous = generator.throw(e) - else: - previous = generator.send(previous_result) - yield previous - except StopIteration as e: - yield e.value - return - - -class MockTask(): - def __init__(self, result=None): - self.result = result - - -def mock_activity(activity_name, input): +@patch('azure.durable_functions.models.TaskBase') +def mock_activity(activity_name, input, task): if activity_name == "say_hello": - return MockTask(f"Hello {input}!") + task.result = f"Hello {input}!" + return task raise Exception("Activity not found") class TestFunction(unittest.TestCase): @patch('azure.durable_functions.DurableOrchestrationContext') - def test_chaining_orchestrator(self, context): + def test_my_orchestrator(self, context): # Get the original method definition as seen in the function_app.py file func_call = my_orchestrator.build().get_user_function().orchestrator_function @@ -46,8 +23,9 @@ def test_chaining_orchestrator(self, context): # Create a generator using the method and mocked context user_orchestrator = func_call(context) - # Use a method defined above to get the values from the generator. Quick unwrap for easy access - values = [val for val in orchestrator_generator_wrapper(user_orchestrator)] + # Use OrchestratorGeneratorWrapper to get the values from the generator. + # Processes the orchestrator in a way that is equivalent to the Durable replay logic + values = [val for val in OrchestratorGeneratorWrapper.wrap(user_orchestrator)] expected_activity_calls = [call('say_hello', 'Tokyo'), call('say_hello', 'Seattle'), diff --git a/samples-v2/fan_in_fan_out/tests/test_E2_BackupSiteContent.py b/samples-v2/fan_in_fan_out/tests/test_E2_BackupSiteContent.py index 24c4b3db..8db91ebc 100644 --- a/samples-v2/fan_in_fan_out/tests/test_E2_BackupSiteContent.py +++ b/samples-v2/fan_in_fan_out/tests/test_E2_BackupSiteContent.py @@ -1,38 +1,20 @@ import unittest from unittest.mock import Mock, call, patch +from azure.durable_functions.testing import OrchestratorGeneratorWrapper from function_app import E2_BackupSiteContent -# A way to wrap an orchestrator generator to simplify calling it and getting the results. -# Because orchestrators in Durable Functions always accept the result of the previous activity for the next send() call, -# we can unwrap the orchestrator generator using this method to simplify per-test code. -def orchestrator_generator_wrapper(generator): - previous = next(generator) - yield previous - while True: - try: - previous_result = None - try: - previous_result = previous.result - except Exception as e: # Simulated activity exceptions, timer interrupted exceptions, anytime a task would throw. - previous = generator.throw(e) - else: - previous = generator.send(previous_result) - yield previous - except StopIteration as e: - yield e.value - return - - -class MockTask(): - def __init__(self, result=None): - self.result = result - - -def mock_activity(activity_name, input): + +@patch('azure.durable_functions.models.TaskBase') +def create_mock_task(result, task): + task.result = result + return task + + +def mock_activity(activity_name, input, task): if activity_name == "E2_GetFileList": - return MockTask(["C:/test/E2_Activity.py", "C:/test/E2_Orchestrator.py"]) - return MockTask(input) + return create_mock_task(["C:/test/E2_Activity.py", "C:/test/E2_Orchestrator.py"]) + raise Exception("Activity not found") class TestFunction(unittest.TestCase): @@ -43,13 +25,14 @@ def test_E2_BackupSiteContent(self, context): context.get_input = Mock(return_value="C:/test") context.call_activity = Mock(side_effect=mock_activity) - context.task_all = Mock(return_value=MockTask([100, 200, 300])) + context.task_all = Mock(return_value=create_mock_task([100, 200, 300])) # Execute the function code user_orchestrator = func_call(context) - # Use a method defined above to get the values from the generator. Quick unwrap for easy access - values = [val for val in orchestrator_generator_wrapper(user_orchestrator)] + # Use OrchestratorGeneratorWrapper to get the values from the generator. + # Processes the orchestrator in a way that is equivalent to the Durable replay logic + values = [val for val in OrchestratorGeneratorWrapper.wrap(user_orchestrator)] expected_activity_calls = [call('E2_GetFileList', 'C:/test'), call('E2_CopyFileToBlob', 'C:/test/E2_Activity.py'), diff --git a/samples-v2/function_chaining/tests/test_my_orchestrator.py b/samples-v2/function_chaining/tests/test_my_orchestrator.py index 092a1b84..5bcb3ad9 100644 --- a/samples-v2/function_chaining/tests/test_my_orchestrator.py +++ b/samples-v2/function_chaining/tests/test_my_orchestrator.py @@ -1,38 +1,16 @@ from datetime import timedelta import unittest from unittest.mock import Mock, call, patch +from azure.durable_functions.testing import OrchestratorGeneratorWrapper from function_app import my_orchestrator -# A way to wrap an orchestrator generator to simplify calling it and getting the results. -# Because orchestrators in Durable Functions always accept the result of the previous activity for the next send() call, -# we can unwrap the orchestrator generator using this method to simplify per-test code. -def orchestrator_generator_wrapper(generator): - previous = next(generator) - yield previous - while True: - try: - previous_result = None - try: - previous_result = previous.result - except Exception as e: # Simulated activity exceptions, timer interrupted exceptions, anytime a task would throw. - previous = generator.throw(e) - else: - previous = generator.send(previous_result) - yield previous - except StopIteration as e: - yield e.value - return - - -class MockTask(): - def __init__(self, result=None): - self.result = result - - -def mock_activity(activity_name, input): + +@patch('azure.durable_functions.models.TaskBase') +def mock_activity(activity_name, input, task): if activity_name == "say_hello": - return MockTask(f"Hello {input}!") + task.result = f"Hello {input}!" + return task raise Exception("Activity not found") @@ -47,8 +25,9 @@ def test_chaining_orchestrator(self, context): # Create a generator using the method and mocked context user_orchestrator = func_call(context) - # Use a method defined above to get the values from the generator. Quick unwrap for easy access - values = [val for val in orchestrator_generator_wrapper(user_orchestrator)] + # Use OrchestratorGeneratorWrapper to get the values from the generator. + # Processes the orchestrator in a way that is equivalent to the Durable replay logic + values = [val for val in OrchestratorGeneratorWrapper.wrap(user_orchestrator)] expected_activity_calls = [call('say_hello', 'Tokyo'), call('say_hello', 'Seattle'), From 80e5d62b88113cb7ae2899cb2922587e01006322 Mon Sep 17 00:00:00 2001 From: andystaples <77818326+andystaples@users.noreply.github.com> Date: Fri, 9 May 2025 15:26:39 -0600 Subject: [PATCH 2/5] Delete azure-functions-durable-python.sln --- azure-functions-durable-python.sln | 29 ----------------------------- 1 file changed, 29 deletions(-) delete mode 100644 azure-functions-durable-python.sln diff --git a/azure-functions-durable-python.sln b/azure-functions-durable-python.sln deleted file mode 100644 index 81511d0c..00000000 --- a/azure-functions-durable-python.sln +++ /dev/null @@ -1,29 +0,0 @@ -Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio Version 17 -VisualStudioVersion = 17.5.2.0 -MinimumVisualStudioVersion = 10.0.40219.1 -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "samples", "samples", "{5D20AA90-6969-D8BD-9DCD-8634F4692FDA}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "extensions", "samples\aml_monitoring\extensions.csproj", "{33E598B8-4178-679F-9B92-BE8D8A64F1A5}" -EndProject -Global - GlobalSection(SolutionConfigurationPlatforms) = preSolution - Debug|Any CPU = Debug|Any CPU - Release|Any CPU = Release|Any CPU - EndGlobalSection - GlobalSection(ProjectConfigurationPlatforms) = postSolution - {33E598B8-4178-679F-9B92-BE8D8A64F1A5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {33E598B8-4178-679F-9B92-BE8D8A64F1A5}.Debug|Any CPU.Build.0 = Debug|Any CPU - {33E598B8-4178-679F-9B92-BE8D8A64F1A5}.Release|Any CPU.ActiveCfg = Release|Any CPU - {33E598B8-4178-679F-9B92-BE8D8A64F1A5}.Release|Any CPU.Build.0 = Release|Any CPU - EndGlobalSection - GlobalSection(SolutionProperties) = preSolution - HideSolutionNode = FALSE - EndGlobalSection - GlobalSection(NestedProjects) = preSolution - {33E598B8-4178-679F-9B92-BE8D8A64F1A5} = {5D20AA90-6969-D8BD-9DCD-8634F4692FDA} - EndGlobalSection - GlobalSection(ExtensibilityGlobals) = postSolution - SolutionGuid = {7D002670-7479-46AF-99FF-1EBAB72B6D22} - EndGlobalSection -EndGlobal From 06f038a07ca2e1e63bf2d344240de1e3a28a7f79 Mon Sep 17 00:00:00 2001 From: Andy Staples Date: Mon, 12 May 2025 16:12:21 -0600 Subject: [PATCH 3/5] Fix type hinting --- .../testing/OrchestratorGeneratorWrapper.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/azure/durable_functions/testing/OrchestratorGeneratorWrapper.py b/azure/durable_functions/testing/OrchestratorGeneratorWrapper.py index 254482ec..5bb3ef30 100644 --- a/azure/durable_functions/testing/OrchestratorGeneratorWrapper.py +++ b/azure/durable_functions/testing/OrchestratorGeneratorWrapper.py @@ -1,4 +1,4 @@ -from typing import Generator, Any +from typing import Generator, Any, Union from azure.durable_functions.models import TaskBase class OrchestratorGeneratorWrapper: @@ -9,7 +9,7 @@ class OrchestratorGeneratorWrapper: """ @staticmethod - def wrap(generator: Generator[TaskBase, Any, Any]) -> Generator[TaskBase, None, Any]: + def wrap(generator: Generator[TaskBase, Any, Any]) -> Generator[Union[TaskBase, Any], None, None]: """Wraps the generator to simulate the Durable replay logic. Parameters @@ -21,7 +21,7 @@ def wrap(generator: Generator[TaskBase, Any, Any]) -> Generator[TaskBase, None, Returns ------- - Generator[TaskBase, None, Any] + Generator[Union[TaskBase, Any], None, None] A simplified version of the orchestrator which takes no inputs. This generator will yield back the TaskBase objects that are yielded from the user orchestrator as well as the final result of the orchestrator. Exception handling is also simulated here From ff674e669c567be33fa351ac71b087567ec8395a Mon Sep 17 00:00:00 2001 From: Andy Staples Date: Tue, 13 May 2025 14:23:41 -0600 Subject: [PATCH 4/5] Refactor to function --- azure-functions-durable-python.sln | 29 ++++++++ .../testing/OrchestratorGeneratorWrapper.py | 69 +++++++++---------- azure/durable_functions/testing/__init__.py | 4 +- .../blueprint/tests/test_my_orchestrator.py | 7 +- .../tests/test_E2_BackupSiteContent.py | 6 +- .../tests/test_my_orchestrator.py | 7 +- 6 files changed, 71 insertions(+), 51 deletions(-) create mode 100644 azure-functions-durable-python.sln diff --git a/azure-functions-durable-python.sln b/azure-functions-durable-python.sln new file mode 100644 index 00000000..989543d2 --- /dev/null +++ b/azure-functions-durable-python.sln @@ -0,0 +1,29 @@ +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 17 +VisualStudioVersion = 17.5.2.0 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "samples", "samples", "{5D20AA90-6969-D8BD-9DCD-8634F4692FDA}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "extensions", "samples\aml_monitoring\extensions.csproj", "{33E598B8-4178-679F-9B92-BE8D8A64F1A5}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {33E598B8-4178-679F-9B92-BE8D8A64F1A5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {33E598B8-4178-679F-9B92-BE8D8A64F1A5}.Debug|Any CPU.Build.0 = Debug|Any CPU + {33E598B8-4178-679F-9B92-BE8D8A64F1A5}.Release|Any CPU.ActiveCfg = Release|Any CPU + {33E598B8-4178-679F-9B92-BE8D8A64F1A5}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(NestedProjects) = preSolution + {33E598B8-4178-679F-9B92-BE8D8A64F1A5} = {5D20AA90-6969-D8BD-9DCD-8634F4692FDA} + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {AEA3AC93-4361-47CD-A8C7-CA280ABE1BDC} + EndGlobalSection +EndGlobal diff --git a/azure/durable_functions/testing/OrchestratorGeneratorWrapper.py b/azure/durable_functions/testing/OrchestratorGeneratorWrapper.py index 5bb3ef30..fc159974 100644 --- a/azure/durable_functions/testing/OrchestratorGeneratorWrapper.py +++ b/azure/durable_functions/testing/OrchestratorGeneratorWrapper.py @@ -1,45 +1,38 @@ from typing import Generator, Any, Union from azure.durable_functions.models import TaskBase -class OrchestratorGeneratorWrapper: - """A helper class for unit testing orchestrator functions. - Defines a method that will unwrap a user orchestrator function in a way that is similar to the - Durable replay logic, but without saving state for the Durable Task Hub. - """ - - @staticmethod - def wrap(generator: Generator[TaskBase, Any, Any]) -> Generator[Union[TaskBase, Any], None, None]: - """Wraps the generator to simulate the Durable replay logic. +def orchestrator_generator_wrapper(generator: Generator[TaskBase, Any, Any]) -> Generator[Union[TaskBase, Any], None, None]: + """Wraps a user-defined orchestrator function to simulate the Durable replay logic. - Parameters - ---------- - generator: Generator[TaskBase, Any, Any] - Generator orchestrator as defined in the user function app. This generator is expected - to yield a series of TaskBase objects and recieve the results of these tasks until - returning the result of the orchestrator. + Parameters + ---------- + generator: Generator[TaskBase, Any, Any] + Generator orchestrator as defined in the user function app. This generator is expected + to yield a series of TaskBase objects and recieve the results of these tasks until + returning the result of the orchestrator. - Returns - ------- - Generator[Union[TaskBase, Any], None, None] - A simplified version of the orchestrator which takes no inputs. This generator will - yield back the TaskBase objects that are yielded from the user orchestrator as well - as the final result of the orchestrator. Exception handling is also simulated here - in the same way as replay, where tasks returning exceptions are thrown back into the - orchestrator. - """ - previous = next(generator) - yield previous - while True: + Returns + ------- + Generator[Union[TaskBase, Any], None, None] + A simplified version of the orchestrator which takes no inputs. This generator will + yield back the TaskBase objects that are yielded from the user orchestrator as well + as the final result of the orchestrator. Exception handling is also simulated here + in the same way as replay, where tasks returning exceptions are thrown back into the + orchestrator. + """ + previous = next(generator) + yield previous + while True: + try: + previous_result = None try: - previous_result = None - try: - previous_result = previous.result - except Exception as e: # Simulated activity exceptions, timer interrupted exceptions, anytime a task would throw. - previous = generator.throw(e) - else: - previous = generator.send(previous_result) - yield previous - except StopIteration as e: - yield e.value - return \ No newline at end of file + previous_result = previous.result + except Exception as e: # Simulated activity exceptions, timer interrupted exceptions, anytime a task would throw. + previous = generator.throw(e) + else: + previous = generator.send(previous_result) + yield previous + except StopIteration as e: + yield e.value + return \ No newline at end of file diff --git a/azure/durable_functions/testing/__init__.py b/azure/durable_functions/testing/__init__.py index a7bd4cfe..19a21681 100644 --- a/azure/durable_functions/testing/__init__.py +++ b/azure/durable_functions/testing/__init__.py @@ -1,6 +1,6 @@ """Unit testing utilities for Azure Durable functions.""" -from .OrchestratorGeneratorWrapper import OrchestratorGeneratorWrapper +from .OrchestratorGeneratorWrapper import orchestrator_generator_wrapper __all__ = [ - 'OrchestratorGeneratorWrapper' + 'orchestrator_generator_wrapper' ] diff --git a/samples-v2/blueprint/tests/test_my_orchestrator.py b/samples-v2/blueprint/tests/test_my_orchestrator.py index 60d8b977..f9893261 100644 --- a/samples-v2/blueprint/tests/test_my_orchestrator.py +++ b/samples-v2/blueprint/tests/test_my_orchestrator.py @@ -1,7 +1,6 @@ -from datetime import timedelta import unittest from unittest.mock import Mock, call, patch -from azure.durable_functions.testing import OrchestratorGeneratorWrapper +from azure.durable_functions.testing import orchestrator_generator_wrapper from durable_blueprints import my_orchestrator @@ -23,9 +22,9 @@ def test_my_orchestrator(self, context): # Create a generator using the method and mocked context user_orchestrator = func_call(context) - # Use OrchestratorGeneratorWrapper to get the values from the generator. + # Use orchestrator_generator_wrapper to get the values from the generator. # Processes the orchestrator in a way that is equivalent to the Durable replay logic - values = [val for val in OrchestratorGeneratorWrapper.wrap(user_orchestrator)] + values = [val for val in orchestrator_generator_wrapper(user_orchestrator)] expected_activity_calls = [call('say_hello', 'Tokyo'), call('say_hello', 'Seattle'), diff --git a/samples-v2/fan_in_fan_out/tests/test_E2_BackupSiteContent.py b/samples-v2/fan_in_fan_out/tests/test_E2_BackupSiteContent.py index 8db91ebc..1e154bd7 100644 --- a/samples-v2/fan_in_fan_out/tests/test_E2_BackupSiteContent.py +++ b/samples-v2/fan_in_fan_out/tests/test_E2_BackupSiteContent.py @@ -1,6 +1,6 @@ import unittest from unittest.mock import Mock, call, patch -from azure.durable_functions.testing import OrchestratorGeneratorWrapper +from azure.durable_functions.testing import orchestrator_generator_wrapper from function_app import E2_BackupSiteContent @@ -30,9 +30,9 @@ def test_E2_BackupSiteContent(self, context): # Execute the function code user_orchestrator = func_call(context) - # Use OrchestratorGeneratorWrapper to get the values from the generator. + # Use orchestrator_generator_wrapper to get the values from the generator. # Processes the orchestrator in a way that is equivalent to the Durable replay logic - values = [val for val in OrchestratorGeneratorWrapper.wrap(user_orchestrator)] + values = [val for val in orchestrator_generator_wrapper(user_orchestrator)] expected_activity_calls = [call('E2_GetFileList', 'C:/test'), call('E2_CopyFileToBlob', 'C:/test/E2_Activity.py'), diff --git a/samples-v2/function_chaining/tests/test_my_orchestrator.py b/samples-v2/function_chaining/tests/test_my_orchestrator.py index 5bcb3ad9..a1b5efe6 100644 --- a/samples-v2/function_chaining/tests/test_my_orchestrator.py +++ b/samples-v2/function_chaining/tests/test_my_orchestrator.py @@ -1,7 +1,6 @@ -from datetime import timedelta import unittest from unittest.mock import Mock, call, patch -from azure.durable_functions.testing import OrchestratorGeneratorWrapper +from azure.durable_functions.testing import orchestrator_generator_wrapper from function_app import my_orchestrator @@ -25,9 +24,9 @@ def test_chaining_orchestrator(self, context): # Create a generator using the method and mocked context user_orchestrator = func_call(context) - # Use OrchestratorGeneratorWrapper to get the values from the generator. + # Use orchestrator_generator_wrapper to get the values from the generator. # Processes the orchestrator in a way that is equivalent to the Durable replay logic - values = [val for val in OrchestratorGeneratorWrapper.wrap(user_orchestrator)] + values = [val for val in orchestrator_generator_wrapper(user_orchestrator)] expected_activity_calls = [call('say_hello', 'Tokyo'), call('say_hello', 'Seattle'), From 7f4e5be6ab7b119160acda9949e00c897c78b6a4 Mon Sep 17 00:00:00 2001 From: Andy Staples Date: Tue, 13 May 2025 14:25:08 -0600 Subject: [PATCH 5/5] Spelling --- azure/durable_functions/testing/OrchestratorGeneratorWrapper.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/azure/durable_functions/testing/OrchestratorGeneratorWrapper.py b/azure/durable_functions/testing/OrchestratorGeneratorWrapper.py index fc159974..9790c8b3 100644 --- a/azure/durable_functions/testing/OrchestratorGeneratorWrapper.py +++ b/azure/durable_functions/testing/OrchestratorGeneratorWrapper.py @@ -9,7 +9,7 @@ def orchestrator_generator_wrapper(generator: Generator[TaskBase, Any, Any]) -> ---------- generator: Generator[TaskBase, Any, Any] Generator orchestrator as defined in the user function app. This generator is expected - to yield a series of TaskBase objects and recieve the results of these tasks until + to yield a series of TaskBase objects and receive the results of these tasks until returning the result of the orchestrator. Returns