Skip to content

Commit ca03832

Browse files
authored
Merge pull request #1333 from Sage-Bionetworks/SYNPY-1784
[SYNPY-1784] Added delete_record_set and delete_file_view parameters to CurationTask.delete()
2 parents 5ca3142 + 6b20dac commit ca03832

3 files changed

Lines changed: 223 additions & 8 deletions

File tree

docs/guides/extensions/curator/metadata_curation.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -395,6 +395,9 @@ if validation_df is not None:
395395
# Clean up temporary file
396396
if os.path.exists(temp_csv):
397397
os.unlink(temp_csv)
398+
399+
# Clean up RecordSet and CurationTask
400+
curation_task.delete(delete_source=True)
398401
```
399402

400403
In this example you would expect to get results like:

synapseclient/models/curation.py

Lines changed: 88 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ class FileBasedMetadataTaskProperties:
4545
A CurationTaskProperties for file-based data, describing where data is uploaded
4646
and a view which contains the annotations.
4747
48-
Represents a [Synapse FileBasedMetadataTaskProperties](https://rest-docs.synapse.org/org/sagebionetworks/repo/model/curation/metadata/FileBasedMetadataTaskProperties.html).
48+
Represents a [Synapse FileBasedMetadataTaskProperties](https://rest-docs.synapse.org/rest/org/sagebionetworks/repo/model/curation/metadata/FileBasedMetadataTaskProperties.html).
4949
5050
Attributes:
5151
upload_folder_id: The synId of the folder where data files of this type are to be uploaded
@@ -94,7 +94,7 @@ class RecordBasedMetadataTaskProperties:
9494
"""
9595
A CurationTaskProperties for record-based metadata.
9696
97-
Represents a [Synapse RecordBasedMetadataTaskProperties](https://rest-docs.synapse.org/org/sagebionetworks/repo/model/curation/metadata/RecordBasedMetadataTaskProperties.html).
97+
Represents a [Synapse RecordBasedMetadataTaskProperties](https://rest-docs.synapse.org/rest/org/sagebionetworks/repo/model/curation/metadata/RecordBasedMetadataTaskProperties.html).
9898
9999
Attributes:
100100
record_set_id: The synId of the RecordSet that will contain all record-based metadata
@@ -213,11 +213,18 @@ def get(self, *, synapse_client: Optional[Synapse] = None) -> "CurationTask":
213213
"""
214214
return self
215215

216-
def delete(self, *, synapse_client: Optional[Synapse] = None) -> None:
216+
def delete(
217+
self,
218+
delete_source: bool = False,
219+
*,
220+
synapse_client: Optional[Synapse] = None,
221+
) -> None:
217222
"""
218223
Deletes a CurationTask from Synapse.
219224
220225
Arguments:
226+
delete_source: If True, the associated source data (EntityView or RecordSet) will also be deleted
227+
if the task is a FileBasedMetadataTask or RecordBasedMetadataTask respectively. Defaults to False.
221228
synapse_client: If not passed in and caching was not disabled by
222229
`Synapse.allow_client_caching(False)` this will use the last created
223230
instance from the Synapse class constructor.
@@ -238,6 +245,20 @@ def delete(self, *, synapse_client: Optional[Synapse] = None) -> None:
238245
task = CurationTask(task_id=123)
239246
task.delete()
240247
```
248+
249+
Example: Delete a curation task and its associated data source
250+
 
251+
252+
```python
253+
from synapseclient import Synapse
254+
from synapseclient.models import CurationTask
255+
256+
syn = Synapse()
257+
syn.login()
258+
259+
task = CurationTask(task_id=123)
260+
task.delete(delete_source=True)
261+
```
241262
"""
242263
return None
243264

@@ -602,17 +623,26 @@ async def main():
602623
self._set_last_persistent_instance()
603624
return self
604625

605-
async def delete_async(self, *, synapse_client: Optional[Synapse] = None) -> None:
626+
async def delete_async(
627+
self,
628+
delete_source: bool = False,
629+
*,
630+
synapse_client: Optional[Synapse] = None,
631+
) -> None:
606632
"""
607633
Deletes a CurationTask from Synapse.
608634
609635
Arguments:
636+
delete_source: If True, the associated source data (EntityView or RecordSet) will also be deleted
637+
if the task is a FileBasedMetadataTask or RecordBasedMetadataTask respectively. Defaults to False.
610638
synapse_client: If not passed in and caching was not disabled by
611639
`Synapse.allow_client_caching(False)` this will use the last created
612640
instance from the Synapse class constructor.
613641
614642
Raises:
615643
ValueError: If the CurationTask object does not have a task_id.
644+
ValueError: If delete_source is True but the task properties are not properly set
645+
to identify the source to delete.
616646
617647
Example: Delete a curation task asynchronously
618648
 
@@ -632,6 +662,25 @@ async def main():
632662
633663
asyncio.run(main())
634664
```
665+
666+
Example: Delete a curation task and its associated data source asynchronously
667+
 
668+
669+
```python
670+
import asyncio
671+
from synapseclient import Synapse
672+
from synapseclient.models import CurationTask
673+
674+
syn = Synapse()
675+
syn.login()
676+
677+
async def main():
678+
task = CurationTask(task_id=123)
679+
await task.delete_async(delete_source=True)
680+
print("Task and record set deleted successfully")
681+
682+
asyncio.run(main())
683+
```
635684
"""
636685
if not self.task_id:
637686
raise ValueError("task_id is required to delete a CurationTask")
@@ -642,6 +691,41 @@ async def main():
642691
}
643692
)
644693

694+
if delete_source:
695+
if not self.task_properties:
696+
await self.get_async(synapse_client=synapse_client)
697+
698+
if isinstance(self.task_properties, FileBasedMetadataTaskProperties):
699+
if not self.task_properties.file_view_id:
700+
raise ValueError(
701+
"Cannot delete Fileview: "
702+
"'file_view_id' attribute is missing."
703+
)
704+
from synapseclient.models import EntityView
705+
706+
await EntityView(id=self.task_properties.file_view_id).delete_async(
707+
synapse_client=synapse_client
708+
)
709+
710+
elif isinstance(self.task_properties, RecordBasedMetadataTaskProperties):
711+
if not self.task_properties.record_set_id:
712+
raise ValueError(
713+
"Cannot delete RecordSet: "
714+
"'record_set_id' attribute is missing."
715+
)
716+
from synapseclient.models import RecordSet
717+
718+
await RecordSet(id=self.task_properties.record_set_id).delete_async(
719+
synapse_client=synapse_client
720+
)
721+
722+
else:
723+
raise ValueError(
724+
"'task_property' attribute is None. "
725+
"Deletion only supports FileBasedMetadataTaskProperties or "
726+
"RecordBasedMetadataTaskProperties."
727+
)
728+
645729
await delete_curation_task(task_id=self.task_id, synapse_client=synapse_client)
646730

647731
async def store_async(

tests/integration/synapseclient/models/async/test_curation_async.py

Lines changed: 132 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010

1111
from synapseclient import Synapse
1212
from synapseclient.core.exceptions import SynapseHTTPError
13+
from synapseclient.core.utils import make_bogus_uuid_file
1314
from synapseclient.models import (
1415
Column,
1516
ColumnType,
@@ -370,7 +371,7 @@ def init(self, syn: Synapse, schedule_for_cleanup: Callable[..., None]) -> None:
370371
self.syn = syn
371372
self.schedule_for_cleanup = schedule_for_cleanup
372373

373-
@pytest.fixture(scope="class")
374+
@pytest.fixture(scope="function")
374375
async def folder_with_view(
375376
self,
376377
project_model: Project,
@@ -412,7 +413,35 @@ async def folder_with_view(
412413

413414
return folder, entity_view
414415

415-
async def test_delete_curation_task_async(
416+
@pytest.fixture(scope="function")
417+
async def folder_with_record_set(
418+
self,
419+
project_model: Project,
420+
syn: Synapse,
421+
schedule_for_cleanup: Callable[..., None],
422+
) -> tuple[Folder, EntityView]:
423+
"""Create a folder with a a record set for record-based testing."""
424+
# Create a folder
425+
folder = await Folder(
426+
name=str(uuid.uuid4()),
427+
parent_id=project_model.id,
428+
).store_async(synapse_client=syn)
429+
schedule_for_cleanup(folder.id)
430+
431+
filename = make_bogus_uuid_file()
432+
schedule_for_cleanup(filename)
433+
434+
record_set = await RecordSet(
435+
name=str(uuid.uuid4()),
436+
parent_id=folder.id,
437+
path=filename,
438+
upsert_keys=["xxx"],
439+
).store_async(synapse_client=syn)
440+
schedule_for_cleanup(record_set.id)
441+
442+
return folder, record_set
443+
444+
async def test_delete_file_based_curation_task_async(
416445
self, project_model: Project, folder_with_view: tuple[Folder, EntityView]
417446
) -> None:
418447
# GIVEN a project, folder, and entity view
@@ -434,13 +463,112 @@ async def test_delete_curation_task_async(
434463
task_id = curation_task.task_id
435464
assert task_id is not None
436465

437-
# WHEN I delete the task asynchronously
438-
await curation_task.delete_async(synapse_client=self.syn)
466+
# WHEN I delete the task asynchronously, without deleting the file view
467+
await curation_task.delete_async(synapse_client=self.syn, delete_source=False)
468+
469+
# THEN the task should be deleted and no longer retrievable
470+
with pytest.raises(SynapseHTTPError):
471+
await CurationTask(task_id=task_id).get_async(synapse_client=self.syn)
472+
473+
# AND the file view should not be deleted
474+
await EntityView(entity_view.id).get_async(synapse_client=self.syn)
475+
476+
async def test_delete_file_based_curation_task_and_fileview_async(
477+
self, project_model: Project, folder_with_view: tuple[Folder, EntityView]
478+
) -> None:
479+
# GIVEN a project, folder, and entity view
480+
folder, entity_view = folder_with_view
481+
482+
# GIVEN an existing curation task
483+
data_type = f"test_data_type_{str(uuid.uuid4()).replace('-', '_')}"
484+
task_properties = FileBasedMetadataTaskProperties(
485+
upload_folder_id=folder.id,
486+
file_view_id=entity_view.id,
487+
)
488+
curation_task = await CurationTask(
489+
data_type=data_type,
490+
project_id=project_model.id,
491+
instructions="Task to be deleted",
492+
task_properties=task_properties,
493+
).store_async(synapse_client=self.syn)
494+
495+
task_id = curation_task.task_id
496+
assert task_id is not None
497+
498+
# WHEN I delete the task and fileview asynchronously
499+
await curation_task.delete_async(synapse_client=self.syn, delete_source=True)
500+
501+
# THEN the task should be deleted and no longer retrievable
502+
with pytest.raises(SynapseHTTPError):
503+
await CurationTask(task_id=task_id).get_async(synapse_client=self.syn)
504+
505+
# AND the file view should be deleted and no longer retrievable
506+
with pytest.raises(SynapseHTTPError):
507+
await EntityView(entity_view.id).get_async(synapse_client=self.syn)
508+
509+
async def test_delete_record_based_curation_task_async(
510+
self, project_model: Project, folder_with_record_set: tuple[Folder, EntityView]
511+
) -> None:
512+
# GIVEN a folder, and record set
513+
_, record_set = folder_with_record_set
514+
515+
# GIVEN an existing curation task
516+
data_type = f"test_data_type_{str(uuid.uuid4()).replace('-', '_')}"
517+
task_properties = RecordBasedMetadataTaskProperties(
518+
record_set_id=record_set.id,
519+
)
520+
curation_task = await CurationTask(
521+
data_type=data_type,
522+
project_id=project_model.id,
523+
instructions="Task to be deleted",
524+
task_properties=task_properties,
525+
).store_async(synapse_client=self.syn)
526+
527+
task_id = curation_task.task_id
528+
assert task_id is not None
529+
530+
# WHEN I delete the task asynchronously, without deleting the record set
531+
await curation_task.delete_async(synapse_client=self.syn, delete_source=False)
439532

440533
# THEN the task should be deleted and no longer retrievable
441534
with pytest.raises(SynapseHTTPError):
442535
await CurationTask(task_id=task_id).get_async(synapse_client=self.syn)
443536

537+
# AND the record set should not be deleted
538+
await RecordSet(record_set.id).get_async(synapse_client=self.syn)
539+
540+
async def test_delete_record_based_curation_task_and_record_set_async(
541+
self, project_model: Project, folder_with_record_set: tuple[Folder, EntityView]
542+
) -> None:
543+
# GIVEN a folder, and record set
544+
_, record_set = folder_with_record_set
545+
546+
# GIVEN an existing curation task
547+
data_type = f"test_data_type_{str(uuid.uuid4()).replace('-', '_')}"
548+
task_properties = RecordBasedMetadataTaskProperties(
549+
record_set_id=record_set.id,
550+
)
551+
curation_task = await CurationTask(
552+
data_type=data_type,
553+
project_id=project_model.id,
554+
instructions="Task to be deleted",
555+
task_properties=task_properties,
556+
).store_async(synapse_client=self.syn)
557+
558+
task_id = curation_task.task_id
559+
assert task_id is not None
560+
561+
# WHEN I delete the task asynchronously, without deleting the record set
562+
await curation_task.delete_async(synapse_client=self.syn, delete_source=True)
563+
564+
# THEN the task should be deleted and no longer retrievable
565+
with pytest.raises(SynapseHTTPError):
566+
await CurationTask(task_id=task_id).get_async(synapse_client=self.syn)
567+
568+
# AND the record set should be deleted and not retrievable
569+
with pytest.raises(SynapseHTTPError):
570+
await RecordSet(record_set.id).get_async(synapse_client=self.syn)
571+
444572
async def test_delete_validation_error_async(self) -> None:
445573
# GIVEN a CurationTask without a task_id
446574
curation_task = CurationTask()

0 commit comments

Comments
 (0)