Skip to content

Commit 01e03f1

Browse files
authored
[SYNPY-1426] Deprecate activity services in Synapse client (#1215)
* Enhance Activity Protocol with Parent ID Support and Retrieval Methods - Updated ActivitySynchronousProtocol to accept EntityView and Dataset as valid parent types. - Added support for string IDs as parent entities in store, delete, and disassociate methods. - Introduced a new method `get` to retrieve activities by activity ID or parent entity ID, with version support. - Enhanced integration tests for Activity to cover new retrieval methods and string parent ID functionality. - Improved test cases to ensure proper handling of references and activity properties during storage and retrieval.
1 parent 0969275 commit 01e03f1

File tree

10 files changed

+1725
-298
lines changed

10 files changed

+1725
-298
lines changed

synapseclient/api/__init__.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,17 +25,23 @@
2525
from .entity_factory import get_from_entity_factory
2626
from .entity_services import (
2727
create_access_requirements_if_none,
28+
create_activity,
2829
delete_entity,
2930
delete_entity_acl,
3031
delete_entity_generated_by,
32+
delete_entity_provenance,
33+
get_activity,
3134
get_entities_by_md5,
3235
get_entity,
3336
get_entity_acl,
3437
get_entity_path,
38+
get_entity_provenance,
3539
get_upload_destination,
3640
get_upload_destination_location,
3741
post_entity,
3842
put_entity,
43+
set_entity_provenance,
44+
update_activity,
3945
)
4046
from .file_services import (
4147
AddPartResponse,
@@ -120,6 +126,12 @@
120126
"delete_entity_generated_by",
121127
"get_entity_path",
122128
"get_entities_by_md5",
129+
"get_entity_provenance",
130+
"set_entity_provenance",
131+
"delete_entity_provenance",
132+
"get_activity",
133+
"create_activity",
134+
"update_activity",
123135
# configuration_services
124136
"get_config_file",
125137
"get_config_section_dict",

synapseclient/api/entity_services.py

Lines changed: 325 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88

99
from async_lru import alru_cache
1010

11+
from synapseclient.core.exceptions import SynapseHTTPError
1112
from synapseclient.core.utils import get_synid_and_version
1213

1314
if TYPE_CHECKING:
@@ -542,3 +543,327 @@ async def get_entities_by_md5(
542543
return await client.rest_get_async(
543544
uri=f"/entity/md5/{md5}",
544545
)
546+
547+
548+
async def get_entity_provenance(
549+
entity_id: str,
550+
version_number: Optional[int] = None,
551+
*,
552+
synapse_client: Optional["Synapse"] = None,
553+
) -> Dict[str, Any]:
554+
"""
555+
Retrieve provenance information for a Synapse Entity.
556+
557+
Arguments:
558+
entity_id: The ID of the entity. This may include version `syn123.0` or `syn123`.
559+
If the version is included in `entity_id` and `version_number` is also
560+
passed in, then the version in `entity_id` will be used.
561+
version_number: The version of the Entity to retrieve. Gets the most recent version if omitted.
562+
synapse_client: If not passed in and caching was not disabled by
563+
`Synapse.allow_client_caching(False)` this will use the last created
564+
instance from the Synapse class constructor.
565+
566+
Returns:
567+
Activity object as a dictionary or raises exception if no provenance record exists.
568+
569+
Example: Get provenance for an entity
570+
Get the provenance information for entity `syn123`.
571+
572+
```python
573+
import asyncio
574+
from synapseclient import Synapse
575+
from synapseclient.api import get_entity_provenance
576+
577+
syn = Synapse()
578+
syn.login()
579+
580+
async def main():
581+
activity = await get_entity_provenance(entity_id="syn123")
582+
print(f"Activity: {activity}")
583+
584+
asyncio.run(main())
585+
```
586+
587+
Example: Get provenance for a specific version
588+
Get the provenance information for version 3 of entity `syn123`.
589+
590+
```python
591+
import asyncio
592+
from synapseclient import Synapse
593+
from synapseclient.api import get_entity_provenance
594+
595+
syn = Synapse()
596+
syn.login()
597+
598+
async def main():
599+
activity = await get_entity_provenance(entity_id="syn123", version_number=3)
600+
print(f"Activity: {activity}")
601+
602+
asyncio.run(main())
603+
```
604+
"""
605+
from synapseclient import Synapse
606+
607+
client = Synapse.get_client(synapse_client=synapse_client)
608+
609+
syn_id, syn_version = get_synid_and_version(entity_id)
610+
if not syn_version:
611+
syn_version = version_number
612+
613+
if syn_version:
614+
uri = f"/entity/{syn_id}/version/{syn_version}/generatedBy"
615+
else:
616+
uri = f"/entity/{syn_id}/generatedBy"
617+
618+
return await client.rest_get_async(uri=uri)
619+
620+
621+
async def set_entity_provenance(
622+
entity_id: str,
623+
activity: Dict[str, Any],
624+
*,
625+
synapse_client: Optional["Synapse"] = None,
626+
) -> Dict[str, Any]:
627+
"""
628+
Stores a record of the code and data used to derive a Synapse entity.
629+
630+
Arguments:
631+
entity_id: The ID of the entity.
632+
activity: A dictionary representing an Activity object.
633+
synapse_client: If not passed in and caching was not disabled by
634+
`Synapse.allow_client_caching(False)` this will use the last created
635+
instance from the Synapse class constructor.
636+
637+
Returns:
638+
An updated Activity object as a dictionary.
639+
640+
Example: Set provenance for an entity
641+
Set the provenance for entity `syn123` with an activity.
642+
643+
```python
644+
import asyncio
645+
from synapseclient import Synapse
646+
from synapseclient.api import set_entity_provenance, create_activity
647+
648+
syn = Synapse()
649+
syn.login()
650+
651+
async def main():
652+
# First create or get an activity
653+
activity = await create_activity({
654+
"name": "Analysis Step",
655+
"description": "Data processing step"
656+
})
657+
658+
# Set the provenance
659+
updated_activity = await set_entity_provenance(
660+
entity_id="syn123",
661+
activity=activity
662+
)
663+
print(f"Updated activity: {updated_activity}")
664+
665+
asyncio.run(main())
666+
```
667+
"""
668+
from synapseclient import Synapse
669+
670+
client = Synapse.get_client(synapse_client=synapse_client)
671+
672+
if "id" in activity:
673+
saved_activity = await update_activity(activity, synapse_client=synapse_client)
674+
else:
675+
saved_activity = await create_activity(activity, synapse_client=synapse_client)
676+
677+
uri = f"/entity/{entity_id}/generatedBy?generatedBy={saved_activity['id']}"
678+
return await client.rest_put_async(uri=uri)
679+
680+
681+
async def delete_entity_provenance(
682+
entity_id: str,
683+
*,
684+
synapse_client: Optional["Synapse"] = None,
685+
) -> None:
686+
"""
687+
Removes provenance information from an Entity and deletes the associated Activity.
688+
689+
Arguments:
690+
entity_id: The ID of the entity.
691+
synapse_client: If not passed in and caching was not disabled by
692+
`Synapse.allow_client_caching(False)` this will use the last created
693+
instance from the Synapse class constructor.
694+
695+
Example: Delete provenance for an entity
696+
Delete the provenance for entity `syn123`.
697+
698+
```python
699+
import asyncio
700+
from synapseclient import Synapse
701+
from synapseclient.api import delete_entity_provenance
702+
703+
syn = Synapse()
704+
syn.login()
705+
706+
async def main():
707+
await delete_entity_provenance(entity_id="syn123")
708+
709+
asyncio.run(main())
710+
```
711+
712+
Returns: None
713+
"""
714+
from synapseclient import Synapse
715+
716+
client = Synapse.get_client(synapse_client=synapse_client)
717+
718+
try:
719+
activity = await get_entity_provenance(entity_id, synapse_client=synapse_client)
720+
except SynapseHTTPError:
721+
# If no provenance exists, nothing to delete
722+
return
723+
724+
if not activity:
725+
return
726+
727+
await client.rest_delete_async(uri=f"/entity/{entity_id}/generatedBy")
728+
729+
# If the activity is shared by more than one entity you recieve an HTTP 400 error:
730+
# "If you wish to delete this activity, please first delete all Entities generated by this Activity.""
731+
await client.rest_delete_async(uri=f"/activity/{activity['id']}")
732+
733+
734+
async def create_activity(
735+
activity: Dict[str, Any],
736+
*,
737+
synapse_client: Optional["Synapse"] = None,
738+
) -> Dict[str, Any]:
739+
"""
740+
Create a new Activity in Synapse.
741+
742+
Arguments:
743+
activity: A dictionary representing an Activity object.
744+
synapse_client: If not passed in and caching was not disabled by
745+
`Synapse.allow_client_caching(False)` this will use the last created
746+
instance from the Synapse class constructor.
747+
748+
Returns:
749+
The created Activity object as a dictionary.
750+
751+
Example: Create a new activity
752+
Create a new activity in Synapse.
753+
754+
```python
755+
import asyncio
756+
from synapseclient import Synapse
757+
from synapseclient.api import create_activity
758+
759+
syn = Synapse()
760+
syn.login()
761+
762+
async def main():
763+
activity = await create_activity({
764+
"name": "Data Analysis",
765+
"description": "Processing raw data"
766+
})
767+
print(f"Created activity: {activity}")
768+
769+
asyncio.run(main())
770+
```
771+
"""
772+
from synapseclient import Synapse
773+
774+
client = Synapse.get_client(synapse_client=synapse_client)
775+
return await client.rest_post_async(uri="/activity", body=json.dumps(activity))
776+
777+
778+
async def update_activity(
779+
activity: Dict[str, Any],
780+
*,
781+
synapse_client: Optional["Synapse"] = None,
782+
) -> Dict[str, Any]:
783+
"""
784+
Modifies an existing Activity.
785+
786+
Arguments:
787+
activity: The Activity to be updated. Must contain an 'id' field.
788+
synapse_client: If not passed in and caching was not disabled by
789+
`Synapse.allow_client_caching(False)` this will use the last created
790+
instance from the Synapse class constructor.
791+
792+
Returns:
793+
An updated Activity object as a dictionary.
794+
795+
Raises:
796+
ValueError: If the activity does not contain an 'id' field.
797+
798+
Example: Update an existing activity
799+
Update an existing activity in Synapse.
800+
801+
```python
802+
import asyncio
803+
from synapseclient import Synapse
804+
from synapseclient.api import update_activity
805+
806+
syn = Synapse()
807+
syn.login()
808+
809+
async def main():
810+
activity = {
811+
"id": "12345",
812+
"name": "Updated Analysis",
813+
"description": "Updated processing step"
814+
}
815+
updated_activity = await update_activity(activity)
816+
print(f"Updated activity: {updated_activity}")
817+
818+
asyncio.run(main())
819+
```
820+
"""
821+
from synapseclient import Synapse
822+
823+
if "id" not in activity:
824+
raise ValueError("The activity you want to update must exist on Synapse")
825+
826+
client = Synapse.get_client(synapse_client=synapse_client)
827+
uri = f"/activity/{activity['id']}"
828+
return await client.rest_put_async(uri=uri, body=json.dumps(activity))
829+
830+
831+
async def get_activity(
832+
activity_id: str,
833+
*,
834+
synapse_client: Optional["Synapse"] = None,
835+
) -> Dict[str, Any]:
836+
"""
837+
Retrieve an Activity by its ID.
838+
839+
Arguments:
840+
activity_id: The ID of the activity to retrieve.
841+
synapse_client: If not passed in and caching was not disabled by
842+
`Synapse.allow_client_caching(False)` this will use the last created
843+
instance from the Synapse class constructor.
844+
845+
Returns:
846+
Activity object as a dictionary.
847+
848+
Example: Get activity by ID
849+
Retrieve an activity using its ID.
850+
851+
```python
852+
import asyncio
853+
from synapseclient import Synapse
854+
from synapseclient.api import get_activity
855+
856+
syn = Synapse()
857+
syn.login()
858+
859+
async def main():
860+
activity = await get_activity(activity_id="12345")
861+
print(f"Activity: {activity}")
862+
863+
asyncio.run(main())
864+
```
865+
"""
866+
from synapseclient import Synapse
867+
868+
client = Synapse.get_client(synapse_client=synapse_client)
869+
return await client.rest_get_async(uri=f"/activity/{activity_id}")

0 commit comments

Comments
 (0)