Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions synapseclient/models/submission_status.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
from copy import deepcopy
from dataclasses import dataclass, field, replace
from datetime import date, datetime
from typing import Optional, Protocol, Union
Expand Down Expand Up @@ -435,7 +436,14 @@ def has_changed(self) -> bool:
def _set_last_persistent_instance(self) -> None:
"""Stash the last time this object interacted with Synapse. This is used to
determine if the object has been changed and needs to be updated in Synapse."""
del self._last_persistent_instance
self._last_persistent_instance = replace(self)
self._last_persistent_instance.annotations = (
deepcopy(self.annotations) if self.annotations else {}
)
self._last_persistent_instance.submission_annotations = (
deepcopy(self.submission_annotations) if self.submission_annotations else {}
)
Comment thread
jaymedina marked this conversation as resolved.

def fill_from_dict(
self,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -478,6 +478,71 @@ async def test_batch_update_with_batch_token(self) -> None:
assert request_body["batchToken"] == batch_token
assert request_body["isFirstBatch"] is False

def test_in_place_mutation_of_submission_annotations_detected_by_has_changed(
self,
) -> None:
"""
Test that in-place .update() on submission_annotations are
detected as a change by has_changed.
"""
# GIVEN a status with submission_annotations and a stashed persistent instance
submission_status = SubmissionStatus(
id=SUBMISSION_STATUS_ID,
submission_annotations={"initial_key": ["initial_value"]},
)
submission_status._set_last_persistent_instance()
assert not submission_status.has_changed

# WHEN I mutate submission_annotations in-place via .update()
submission_status.submission_annotations.update({"added_key": ["added_value"]})

# THEN has_changed should detect the mutation
assert submission_status.has_changed

def test_in_place_mutation_of_annotations_detected_by_has_changed(self) -> None:
"""
That that in-place .update() on annotations are detected as
a change by has_changed.
"""
# GIVEN a status with annotations and a stashed persistent instance
submission_status = SubmissionStatus(
id=SUBMISSION_STATUS_ID,
annotations={"initial_key": "initial_value"},
)
submission_status._set_last_persistent_instance()
assert not submission_status.has_changed

# WHEN I mutate annotations in-place via .update()
submission_status.annotations.update({"added_key": "added_value"})

# THEN has_changed should detect the mutation
assert submission_status.has_changed

async def test_store_async_sends_request_after_in_place_mutation(self) -> None:
"""Regression test: store_async must call the API when submission_annotations
was mutated in-place via .update(), not skip it as 'no changes'."""
# GIVEN a status with a stashed persistent instance
submission_status = SubmissionStatus(
id=SUBMISSION_STATUS_ID,
etag=ETAG,
status_version=STATUS_VERSION,
submission_annotations={"initial_key": ["initial_value"]},
)
submission_status._set_last_persistent_instance()

# WHEN I mutate in-place and store
submission_status.submission_annotations.update({"added_key": ["added_value"]})

with patch(
"synapseclient.api.evaluation_services.update_submission_status",
new_callable=AsyncMock,
return_value=self.get_example_submission_status_dict(),
) as mock_update:
await submission_status.store_async(synapse_client=self.syn)

# THEN the API should have been called (not skipped)
mock_update.assert_called_once()

def test_set_last_persistent_instance(self) -> None:
"""Test setting the last persistent instance."""
# GIVEN a SubmissionStatus
Expand Down
Loading