Skip to content

Commit 5ce9e49

Browse files
committed
Add suspend and resume reasons
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 941fc052-b910-42af-93fa-4f00138813ab
1 parent 6dfdbac commit 5ce9e49

6 files changed

Lines changed: 79 additions & 22 deletions

File tree

‎CHANGELOG.md‎

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,12 @@ adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
77

88
## Unreleased
99

10+
ADDED
11+
12+
- Added optional `reason` parameters to `TaskHubGrpcClient` and
13+
`AsyncTaskHubGrpcClient` suspend and resume operations. The reason is now sent
14+
to the backend with the lifecycle request.
15+
1016
## v1.9.0
1117

1218
ADDED

‎azure-functions-durable/CHANGELOG.md‎

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
77

88
## Unreleased
99

10+
FIXED
11+
12+
- Fixed deprecated `DurableFunctionsClient.suspend()` and `resume()` methods
13+
discarding their `reason` arguments. Reasons are now forwarded to the Durable
14+
Task backend.
15+
1016
## v2.0.0b2
1117

1218
ADDED

‎azure-functions-durable/azure/durable_functions/client.py‎

Lines changed: 4 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -431,21 +431,13 @@ async def purge_instance_history(self, instance_id: str) -> PurgeHistoryResult:
431431

432432
@deprecated("suspend is deprecated; use suspend_orchestration instead.")
433433
async def suspend(self, instance_id: str, reason: Optional[str] = None) -> None:
434-
"""Deprecated alias for :meth:`suspend_orchestration`.
435-
436-
The v1 ``reason`` argument has no equivalent in durabletask and is
437-
ignored.
438-
"""
439-
await self.suspend_orchestration(instance_id)
434+
"""Deprecated alias for :meth:`suspend_orchestration`."""
435+
await self.suspend_orchestration(instance_id, reason=reason)
440436

441437
@deprecated("resume is deprecated; use resume_orchestration instead.")
442438
async def resume(self, instance_id: str, reason: Optional[str] = None) -> None:
443-
"""Deprecated alias for :meth:`resume_orchestration`.
444-
445-
The v1 ``reason`` argument has no equivalent in durabletask and is
446-
ignored.
447-
"""
448-
await self.resume_orchestration(instance_id)
439+
"""Deprecated alias for :meth:`resume_orchestration`."""
440+
await self.resume_orchestration(instance_id, reason=reason)
449441

450442
@deprecated("restart is deprecated; use restart_orchestration instead.")
451443
async def restart(

‎durabletask/client.py‎

Lines changed: 24 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -786,13 +786,21 @@ def terminate_orchestration(self, instance_id: str, *,
786786
)
787787
self._stub.TerminateInstance(req)
788788

789-
def suspend_orchestration(self, instance_id: str) -> None:
790-
req = pb.SuspendRequest(instanceId=instance_id)
789+
def suspend_orchestration(self, instance_id: str, *,
790+
reason: str | None = None) -> None:
791+
req = pb.SuspendRequest(
792+
instanceId=instance_id,
793+
reason=helpers.get_string_value(reason),
794+
)
791795
self._logger.info(f"Suspending instance '{instance_id}'.")
792796
self._stub.SuspendInstance(req)
793797

794-
def resume_orchestration(self, instance_id: str) -> None:
795-
req = pb.ResumeRequest(instanceId=instance_id)
798+
def resume_orchestration(self, instance_id: str, *,
799+
reason: str | None = None) -> None:
800+
req = pb.ResumeRequest(
801+
instanceId=instance_id,
802+
reason=helpers.get_string_value(reason),
803+
)
796804
self._logger.info(f"Resuming instance '{instance_id}'.")
797805
self._stub.ResumeInstance(req)
798806

@@ -1320,13 +1328,21 @@ async def terminate_orchestration(self, instance_id: str, *,
13201328
)
13211329
await self._get_stub().TerminateInstance(req)
13221330

1323-
async def suspend_orchestration(self, instance_id: str) -> None:
1324-
req = pb.SuspendRequest(instanceId=instance_id)
1331+
async def suspend_orchestration(self, instance_id: str, *,
1332+
reason: str | None = None) -> None:
1333+
req = pb.SuspendRequest(
1334+
instanceId=instance_id,
1335+
reason=helpers.get_string_value(reason),
1336+
)
13251337
self._logger.info(f"Suspending instance '{instance_id}'.")
13261338
await self._get_stub().SuspendInstance(req)
13271339

1328-
async def resume_orchestration(self, instance_id: str) -> None:
1329-
req = pb.ResumeRequest(instanceId=instance_id)
1340+
async def resume_orchestration(self, instance_id: str, *,
1341+
reason: str | None = None) -> None:
1342+
req = pb.ResumeRequest(
1343+
instanceId=instance_id,
1344+
reason=helpers.get_string_value(reason),
1345+
)
13301346
self._logger.info(f"Resuming instance '{instance_id}'.")
13311347
await self._get_stub().ResumeInstance(req)
13321348

‎tests/azure-functions-durable/test_client_compat.py‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -472,13 +472,13 @@ async def test_suspend_resume_delegate():
472472
new=AsyncMock()) as suspend_mock:
473473
with pytest.warns(DeprecationWarning):
474474
await client.suspend("abc", "reason")
475-
suspend_mock.assert_awaited_once_with("abc")
475+
suspend_mock.assert_awaited_once_with("abc", reason="reason")
476476

477477
with patch.object(client, "resume_orchestration",
478478
new=AsyncMock()) as resume_mock:
479479
with pytest.warns(DeprecationWarning):
480480
await client.resume("abc", "reason")
481-
resume_mock.assert_awaited_once_with("abc")
481+
resume_mock.assert_awaited_once_with("abc", reason="reason")
482482
finally:
483483
await client.close()
484484

‎tests/durabletask/test_client.py‎

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1258,6 +1258,43 @@ def test_worker_stores_resiliency_options():
12581258
assert worker._resiliency_options is resiliency
12591259

12601260

1261+
def test_suspend_resume_orchestration_include_optional_reason():
1262+
stub = MagicMock()
1263+
1264+
with patch('durabletask.client.shared.get_grpc_channel', return_value=MagicMock()), patch(
1265+
'durabletask.client.stubs.TaskHubSidecarServiceStub', return_value=stub):
1266+
client = TaskHubGrpcClient()
1267+
client.suspend_orchestration('suspended', reason='maintenance')
1268+
client.resume_orchestration('resumed')
1269+
1270+
suspend_request = stub.SuspendInstance.call_args.args[0]
1271+
assert suspend_request.instanceId == 'suspended'
1272+
assert suspend_request.reason.value == 'maintenance'
1273+
resume_request = stub.ResumeInstance.call_args.args[0]
1274+
assert resume_request.instanceId == 'resumed'
1275+
assert not resume_request.HasField('reason')
1276+
1277+
1278+
@pytest.mark.asyncio
1279+
async def test_async_suspend_resume_orchestration_include_optional_reason():
1280+
stub = MagicMock()
1281+
stub.SuspendInstance = AsyncMock()
1282+
stub.ResumeInstance = AsyncMock()
1283+
1284+
with patch('durabletask.client.shared.get_async_grpc_channel', return_value=MagicMock()), patch(
1285+
'durabletask.client.stubs.TaskHubSidecarServiceStub', return_value=stub):
1286+
client = AsyncTaskHubGrpcClient()
1287+
await client.suspend_orchestration('suspended')
1288+
await client.resume_orchestration('resumed', reason='maintenance complete')
1289+
1290+
suspend_request = stub.SuspendInstance.call_args.args[0]
1291+
assert suspend_request.instanceId == 'suspended'
1292+
assert not suspend_request.HasField('reason')
1293+
resume_request = stub.ResumeInstance.call_args.args[0]
1294+
assert resume_request.instanceId == 'resumed'
1295+
assert resume_request.reason.value == 'maintenance complete'
1296+
1297+
12611298
def test_get_orchestration_history_aggregates_chunks_and_deexternalizes_payloads():
12621299
store = FakePayloadStore()
12631300
token = store.upload(b'history payload')

0 commit comments

Comments
 (0)