|
4 | 4 | import base64 |
5 | 5 | from functools import wraps |
6 | 6 |
|
7 | | -from durabletask.internal.orchestrator_service_pb2 import OrchestratorRequest, OrchestratorResponse |
| 7 | +from durabletask.internal.orchestrator_service_pb2 import EntityRequest, EntityBatchRequest, EntityBatchResult, OrchestratorRequest, OrchestratorResponse |
8 | 8 | from .metadata import OrchestrationTrigger, ActivityTrigger, EntityTrigger, \ |
9 | 9 | DurableClient |
10 | 10 | from typing import Callable, Optional |
@@ -119,8 +119,49 @@ def _configure_entity_callable(self, wrap) -> Callable: |
119 | 119 | wrapped by the next decorator in the sequence. |
120 | 120 | """ |
121 | 121 | def decorator(entity_func): |
122 | | - # TODO: Implement entity support - similar to orchestrators (?) |
123 | | - raise NotImplementedError() |
| 122 | + # Construct an orchestrator based on the end-user code |
| 123 | + |
| 124 | + # TODO: Move this logic somewhere better |
| 125 | + # TODO: Because this handle method is the one actually exposed to the Functions SDK decorator, |
| 126 | + # the parameter name will always be "context" here, even if the user specified a different name. |
| 127 | + # We need to find a way to allow custom context names (like "ctx"). |
| 128 | + def handle(context) -> str: |
| 129 | + context_body = getattr(context, "body", None) |
| 130 | + if context_body is None: |
| 131 | + context_body = context |
| 132 | + orchestration_context = context_body |
| 133 | + request = EntityBatchRequest() |
| 134 | + request_2 = EntityRequest() |
| 135 | + try: |
| 136 | + request.ParseFromString(base64.b64decode(orchestration_context)) |
| 137 | + except Exception: |
| 138 | + pass |
| 139 | + try: |
| 140 | + request_2.ParseFromString(base64.b64decode(orchestration_context)) |
| 141 | + except Exception: |
| 142 | + pass |
| 143 | + stub = AzureFunctionsNullStub() |
| 144 | + worker = DurableFunctionsWorker() |
| 145 | + response: Optional[EntityBatchResult] = None |
| 146 | + |
| 147 | + def stub_complete(stub_response: EntityBatchResult): |
| 148 | + nonlocal response |
| 149 | + response = stub_response |
| 150 | + stub.CompleteEntityTask = stub_complete |
| 151 | + |
| 152 | + worker.add_entity(entity_func) |
| 153 | + worker._execute_entity_batch(request, stub, None) |
| 154 | + |
| 155 | + if response is None: |
| 156 | + raise Exception("Entity execution did not produce a response.") |
| 157 | + # The Python worker returns the input as type "json", so double-encoding is necessary |
| 158 | + return '"' + base64.b64encode(response.SerializeToString()).decode('utf-8') + '"' |
| 159 | + |
| 160 | + handle.entity_function = entity_func |
| 161 | + |
| 162 | + # invoke next decorator, with the Entity as input |
| 163 | + handle.__name__ = entity_func.__name__ |
| 164 | + return wrap(handle) |
124 | 165 |
|
125 | 166 | return decorator |
126 | 167 |
|
|
0 commit comments