Skip to content

Commit 70bcb0d

Browse files
committed
Address PR feedback
1 parent 4fd837c commit 70bcb0d

4 files changed

Lines changed: 47 additions & 1 deletion

File tree

CHANGELOG.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,13 @@ CHANGED
6161
- JSON serialization failures now raise a `TypeError` that chains the original
6262
error (`__cause__`) and names the offending type.
6363

64+
FIXED
65+
66+
- Falsy entity states (`0`, `""`, `[]`, `{}`) are no longer dropped when an
67+
entity batch is persisted. Previously a falsy current state was treated as
68+
"no state" and written as `None`, effectively deleting it; only an actual
69+
`None` state now clears the persisted entity state.
70+
6471
BREAKING CHANGES (type-level only — no runtime impact for typical users)
6572

6673
These changes do not alter runtime behavior, but because the package ships

durabletask/task.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -181,6 +181,7 @@ def call_entity(self,
181181
entity: EntityInstanceId,
182182
operation: str,
183183
input: Any = ...,
184+
*,
184185
return_type: None = ...) -> CompletableTask[Any]:
185186
...
186187

@@ -189,6 +190,7 @@ def call_entity(self,
189190
entity: EntityInstanceId,
190191
operation: str,
191192
input: Any = None,
193+
*,
192194
return_type: type | None = None) -> CompletableTask[Any]:
193195
"""Schedule entity function for execution.
194196

durabletask/worker.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1376,7 +1376,7 @@ def _execute_entity_batch(
13761376
batch_result = pb.EntityBatchResult(
13771377
results=results,
13781378
actions=entity_state.get_operation_actions(),
1379-
entityState=helpers.get_string_value(self._data_converter.serialize(entity_state._current_state)) if entity_state._current_state else None, # pyright: ignore[reportPrivateUsage]
1379+
entityState=helpers.get_string_value(self._data_converter.serialize(entity_state._current_state)) if entity_state._current_state is not None else None, # pyright: ignore[reportPrivateUsage]
13801380
failureDetails=None,
13811381
completionToken=completionToken,
13821382
operationInfos=operation_infos,
@@ -1716,6 +1716,7 @@ def call_entity(
17161716
entity: EntityInstanceId,
17171717
operation: str,
17181718
input: Any = ...,
1719+
*,
17191720
return_type: None = ...,
17201721
) -> task.CompletableTask[Any]:
17211722
...
@@ -1725,6 +1726,7 @@ def call_entity(
17251726
entity: EntityInstanceId,
17261727
operation: str,
17271728
input: Any = None,
1729+
*,
17281730
return_type: type | None = None,
17291731
) -> task.CompletableTask[Any]:
17301732
id = self.next_sequence_number()

tests/durabletask/test_batch_actions.py

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -385,6 +385,41 @@ def counter_entity(ctx: entities.EntityContext, input):
385385
worker.stop()
386386

387387

388+
def test_entity_falsy_state_is_persisted(backend):
389+
"""A falsy entity state (e.g. 0) must be persisted, not dropped as None.
390+
391+
Regression test: the batch result previously persisted state only when it
392+
was truthy, so a valid falsy state such as ``0`` was written as ``None`` and
393+
effectively deleted. Only an actual ``None`` should clear the state.
394+
"""
395+
def counter_entity(ctx: entities.EntityContext, input):
396+
if ctx.operation == "set":
397+
ctx.set_state(input)
398+
elif ctx.operation == "get":
399+
return ctx.get_state(int)
400+
401+
worker = TaskHubGrpcWorker(host_address=HOST)
402+
403+
worker.add_entity(counter_entity)
404+
worker.start()
405+
406+
try:
407+
with TaskHubGrpcClient(host_address=HOST) as c:
408+
entity_id = entities.EntityInstanceId("counter_entity", "falsyCounter")
409+
# Set the state to a falsy-but-valid value.
410+
c.signal_entity(entity_id, "set", input=0)
411+
time.sleep(3) # Wait for the signal to be processed
412+
413+
query = client.EntityQuery(include_state=True)
414+
all_entities = c.get_all_entities(query)
415+
matches = [e for e in all_entities if e.id == entity_id]
416+
# The entity must still exist with its falsy state intact.
417+
assert len(matches) == 1
418+
assert matches[0].get_state(int) == 0
419+
finally:
420+
worker.stop()
421+
422+
388423
def test_get_entities_by_instance_id_prefix(backend):
389424
def counter_entity(ctx: entities.EntityContext, input):
390425
if ctx.operation == "set":

0 commit comments

Comments
 (0)