Skip to content

Commit 32f6c15

Browse files
committed
Creation of a dry_run flag in delete_permissions and listing the ACL tree given a container or entity
1 parent d9998ef commit 32f6c15

File tree

10 files changed

+5076
-133
lines changed

10 files changed

+5076
-133
lines changed

synapseclient/api/__init__.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929
delete_entity_generated_by,
3030
get_entities_by_md5,
3131
get_entity,
32+
get_entity_acl,
3233
get_entity_path,
3334
get_upload_destination,
3435
get_upload_destination_location,
@@ -55,6 +56,8 @@
5556
get_default_columns,
5657
post_columns,
5758
)
59+
from .team_services import post_team_list
60+
from .user_services import get_user_group_headers_batch
5861

5962
__all__ = [
6063
# annotations
@@ -83,6 +86,7 @@
8386
"post_entity",
8487
"delete_entity",
8588
"delete_entity_acl",
89+
"get_entity_acl",
8690
"get_upload_destination",
8791
"get_upload_destination_location",
8892
"create_access_requirements_if_none",
@@ -110,4 +114,8 @@
110114
"get_default_columns",
111115
"ViewTypeMask",
112116
"ViewEntityType",
117+
# team_services
118+
"post_team_list",
119+
# user_services
120+
"get_user_group_headers_batch",
113121
]

synapseclient/api/entity_services.py

Lines changed: 144 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
"""
44

55
import json
6+
from dataclasses import dataclass
67
from typing import TYPE_CHECKING, Any, Dict, List, Optional, Union
78

89
from async_lru import alru_cache
@@ -13,6 +14,74 @@
1314
from synapseclient import Synapse
1415

1516

17+
@dataclass
18+
class EntityHeader:
19+
"""
20+
JSON schema for EntityHeader POJO. This represents metadata about a Synapse entity.
21+
22+
Attributes:
23+
name: The name of the entity
24+
id: The id of the entity
25+
type: The type of the entity
26+
version_number: The version number of the entity
27+
version_label: The user defined version label of the entity
28+
is_latest_version: If this version is the latest version of the entity
29+
benefactor_id: The ID of the entity that this Entity's ACL is inherited from
30+
created_on: The date this entity was created
31+
modified_on: The date this entity was last modified
32+
created_by: The ID of the user that created this entity
33+
modified_by: The ID of the user that last modified this entity
34+
"""
35+
36+
name: Optional[str] = None
37+
"""The name of the entity"""
38+
39+
id: Optional[str] = None
40+
"""The id of the entity"""
41+
42+
type: Optional[str] = None
43+
"""The type of the entity"""
44+
45+
version_number: Optional[int] = None
46+
"""The version number of the entity"""
47+
48+
version_label: Optional[str] = None
49+
"""The user defined version label of the entity"""
50+
51+
is_latest_version: Optional[bool] = None
52+
"""If this version is the latest version of the entity"""
53+
54+
benefactor_id: Optional[int] = None
55+
"""The ID of the entity that this Entity's ACL is inherited from"""
56+
57+
created_on: Optional[str] = None
58+
"""The date this entity was created"""
59+
60+
modified_on: Optional[str] = None
61+
"""The date this entity was last modified"""
62+
63+
created_by: Optional[str] = None
64+
"""The ID of the user that created this entity"""
65+
66+
modified_by: Optional[str] = None
67+
"""The ID of the user that last modified this entity"""
68+
69+
def fill_from_dict(self, synapse_response: Dict[str, Any]) -> "EntityHeader":
70+
"""Converts a response from the REST API into this dataclass."""
71+
self.name = synapse_response.get("name", None)
72+
self.id = synapse_response.get("id", None)
73+
self.type = synapse_response.get("type", None)
74+
self.version_number = synapse_response.get("versionNumber", None)
75+
self.version_label = synapse_response.get("versionLabel", None)
76+
self.is_latest_version = synapse_response.get("isLatestVersion", None)
77+
self.benefactor_id = synapse_response.get("benefactorId", None)
78+
self.created_on = synapse_response.get("createdOn", None)
79+
self.modified_on = synapse_response.get("modifiedOn", None)
80+
self.created_by = synapse_response.get("createdBy", None)
81+
self.modified_by = synapse_response.get("modifiedBy", None)
82+
return self
83+
84+
1685
async def post_entity(
1786
request: Dict[str, Any],
1887
generated_by: Optional[str] = None,
@@ -345,6 +414,81 @@ async def main():
345414
)
346415

347416

417+
async def get_entity_acl(
418+
entity_id: str,
419+
*,
420+
synapse_client: Optional["Synapse"] = None,
421+
):
422+
"""
423+
Get the Access Control List (ACL) for an entity.
424+
425+
Note: If this method is called on an Entity that is inheriting its permission
426+
from another Entity a NOT_FOUND (404) response will be generated. The error
427+
response message will include the Entity's benefactor ID.
428+
429+
Arguments:
430+
entity_id: The ID of the entity.
431+
synapse_client: If not passed in and caching was not disabled by
432+
`Synapse.allow_client_caching(False)` this will use the last created
433+
instance from the Synapse class constructor.
434+
"""
435+
from synapseclient import Synapse
436+
437+
client = Synapse.get_client(synapse_client=synapse_client)
438+
return await client.rest_get_async(
439+
uri=f"/entity/{entity_id}/acl",
440+
)
441+
442+
443+
async def get_entity_benefactor(
444+
entity_id: str,
445+
*,
446+
synapse_client: Optional["Synapse"] = None,
447+
) -> EntityHeader:
448+
"""
449+
Get an Entity's benefactor. An Entity gets its ACL from its benefactor.
450+
451+
Implements:
452+
<https://rest-docs.synapse.org/rest/GET/entity/id/benefactor.html>
453+
454+
Arguments:
455+
entity_id: The ID of the entity.
456+
synapse_client: If not passed in and caching was not disabled by
457+
`Synapse.allow_client_caching(False)` this will use the last created
458+
instance from the Synapse class constructor.
459+
460+
Returns:
461+
The EntityHeader of the entity's benefactor (the entity from which it inherits its ACL).
462+
463+
Example: Get the benefactor of an entity
464+
Get the benefactor entity header for entity `syn123`.
465+
466+
```python
467+
import asyncio
468+
from synapseclient import Synapse
469+
from synapseclient.api import get_entity_benefactor
470+
471+
syn = Synapse()
472+
syn.login()
473+
474+
async def main():
475+
benefactor = await get_entity_benefactor(entity_id="syn123")
476+
print(f"Entity benefactor: {benefactor.name} (ID: {benefactor.id})")
477+
478+
asyncio.run(main())
479+
```
480+
"""
481+
from synapseclient import Synapse
482+
483+
client = Synapse.get_client(synapse_client=synapse_client)
484+
response = await client.rest_get_async(
485+
uri=f"/entity/{entity_id}/benefactor",
486+
)
487+
488+
entity_header = EntityHeader()
489+
return entity_header.fill_from_dict(response)
490+
491+
348492
async def get_entity_path(
349493
entity_id: str,
350494
*,

synapseclient/api/team_services.py

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
"""This module is responsible for exposing the services defined at:
2+
<https://rest-docs.synapse.org/rest/#org.sagebionetworks.repo.web.controller.TeamController>
3+
"""
4+
5+
import json
6+
from typing import TYPE_CHECKING, Dict, List, Optional, Union
7+
8+
if TYPE_CHECKING:
9+
from synapseclient import Synapse
10+
11+
12+
async def post_team_list(
13+
team_ids: List[int],
14+
*,
15+
synapse_client: Optional["Synapse"] = None,
16+
) -> Optional[List[Dict[str, Union[str, bool]]]]:
17+
"""
18+
Retrieve a list of Teams given their IDs. Invalid IDs in the list are ignored:
19+
The results list is simply smaller than the list of IDs passed in.
20+
21+
Arguments:
22+
team_ids: List of team IDs to retrieve
23+
synapse_client: If not passed in and caching was not disabled by
24+
`Synapse.allow_client_caching(False)` this will use the last created
25+
instance from the Synapse class constructor.
26+
27+
Returns:
28+
List of dictionaries representing <https://rest-docs.synapse.org/rest/org/sagebionetworks/repo/model/Team.html>
29+
30+
Example: Get teams by their IDs
31+
Retrieve specific teams by providing their IDs.
32+
33+
```python
34+
35+
```
36+
"""
37+
from synapseclient import Synapse
38+
39+
client = Synapse.get_client(synapse_client=synapse_client)
40+
41+
request_body = {"list": team_ids}
42+
43+
response = await client.rest_post_async(
44+
uri="/teamList", body=json.dumps(request_body)
45+
)
46+
47+
if "list" in response:
48+
return response["list"] or None
49+
50+
return None

synapseclient/api/user_services.py

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
"""This module is responsible for exposing the services defined at:
2+
<https://rest-docs.synapse.org/rest/#org.sagebionetworks.repo.web.controller.UserGroupController>
3+
"""
4+
5+
from typing import TYPE_CHECKING, Dict, List, Optional, Union
6+
7+
if TYPE_CHECKING:
8+
from synapseclient import Synapse
9+
10+
11+
async def get_user_group_headers_batch(
12+
ids: List[str],
13+
*,
14+
synapse_client: Optional["Synapse"] = None,
15+
) -> List[Dict[str, Union[str, bool]]]:
16+
"""
17+
Batch get UserGroupHeaders. This fetches information about a collection of
18+
users or groups, specified by Synapse IDs.
19+
20+
Arguments:
21+
ids: List of user/group IDs to retrieve
22+
synapse_client: If not passed in and caching was not disabled by
23+
`Synapse.allow_client_caching(False)` this will use the last created
24+
instance from the Synapse class constructor.
25+
26+
Returns:
27+
List representing "children" in
28+
<https://rest-docs.synapse.org/rest/org/sagebionetworks/repo/model/UserGroupHeaderResponsePage.html>
29+
30+
Example: Get user group headers by their IDs
31+
Retrieve specific user/group headers by providing their IDs.
32+
33+
```python
34+
35+
```
36+
"""
37+
from synapseclient import Synapse
38+
39+
client = Synapse.get_client(synapse_client=synapse_client)
40+
41+
ids_param = ",".join(ids)
42+
params = {"ids": ids_param}
43+
44+
response = await client.rest_get_async(uri="/userGroupHeaders/batch", params=params)
45+
46+
if "children" in response:
47+
return response["children"] or []
48+
49+
return []

0 commit comments

Comments
 (0)