Skip to content

Commit 827f8d6

Browse files
committed
Refactor delete_permissions logic to enforce include_container_content with recursive flag and update unit tests for validation
1 parent f9f0c63 commit 827f8d6

File tree

4 files changed

+91
-52
lines changed

4 files changed

+91
-52
lines changed

synapseclient/models/mixins/access_control.py

Lines changed: 29 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -243,12 +243,15 @@ async def delete_permissions_async(
243243
Arguments:
244244
include_self: If True (default), delete the ACL of the current entity.
245245
If False, skip deleting the ACL of the current entity and only process
246-
children if recursive=True or include_container_content=True.
246+
children if recursive=True and include_container_content=True.
247247
recursive: If True and the entity is a container (e.g., Project or Folder),
248-
recursively delete ACLs from all child containers and their children.
248+
recursively process child containers. Note that this must be used with
249+
include_container_content=True to have any effect. Setting recursive=True
250+
with include_container_content=False will raise a ValueError.
249251
Only works on classes that support the `sync_from_synapse_async` method.
250252
include_container_content: If True, delete ACLs from contents directly within
251-
containers (files and folders inside Projects/Folders). Defaults to False.
253+
containers (files and folders inside Projects/Folders). This must be set to
254+
True for recursive to have any effect. Defaults to False.
252255
target_entity_types: Specify which entity types to process when deleting ACLs.
253256
Allowed values are "folder" and "file" (case-insensitive).
254257
If None, defaults to ["folder", "file"].
@@ -292,25 +295,37 @@ async def main():
292295
syn.login()
293296
294297
async def main():
295-
# Delete permissions for this folder and all container entities (folders) within it recursively
296-
await Folder(id="syn123").delete_permissions_async(recursive=True)
298+
# Delete permissions for this folder only (does not affect children)
299+
await Folder(id="syn123").delete_permissions_async()
297300
298-
# Delete permissions for all entities within this folder, but not the folder itself
301+
# Delete permissions for all files and folders directly within this folder,
302+
# but not the folder itself
299303
await Folder(id="syn123").delete_permissions_async(
300304
include_self=False,
301305
include_container_content=True
302306
)
303307
308+
# Delete permissions for all items in the entire hierarchy (folders and their files)
309+
# Both recursive and include_container_content must be True
310+
await Folder(id="syn123").delete_permissions_async(
311+
recursive=True,
312+
include_container_content=True
313+
)
314+
304315
# Delete permissions only for folder entities within this folder recursively
316+
# and their contents
305317
await Folder(id="syn123").delete_permissions_async(
306318
recursive=True,
319+
include_container_content=True,
307320
target_entity_types=["folder"]
308321
)
309322
310-
# Delete all permissions in the entire hierarchy
323+
# Delete permissions only for files within this folder and all subfolders
311324
await Folder(id="syn123").delete_permissions_async(
325+
include_self=False,
312326
recursive=True,
313-
include_container_content=True
327+
include_container_content=True,
328+
target_entity_types=["file"]
314329
)
315330
asyncio.run(main())
316331
```
@@ -327,6 +342,12 @@ async def main():
327342

328343
should_process_children = recursive or include_container_content
329344
if should_process_children and hasattr(self, "sync_from_synapse_async"):
345+
if recursive and not include_container_content:
346+
raise ValueError(
347+
"When recursive=True, include_container_content must also be True. "
348+
"Setting recursive=True with include_container_content=False has no effect."
349+
)
350+
330351
synced = await self._sync_container_structure(client)
331352
if not synced:
332353
return

synapseclient/models/protocols/access_control_protocol.py

Lines changed: 24 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -191,12 +191,15 @@ def delete_permissions(
191191
Arguments:
192192
include_self: If True (default), delete the ACL of the current entity.
193193
If False, skip deleting the ACL of the current entity and only process
194-
children if recursive=True or include_container_content=True.
194+
children if recursive=True and include_container_content=True.
195195
recursive: If True and the entity is a container (e.g., Project or Folder),
196-
recursively delete ACLs from all child containers and their children.
196+
recursively process child containers. Note that this must be used with
197+
include_container_content=True to have any effect. Setting recursive=True
198+
with include_container_content=False will raise a ValueError.
197199
Only works on classes that support the `sync_from_synapse_async` method.
198200
include_container_content: If True, delete ACLs from contents directly within
199-
containers (files and folders inside Projects/Folders). Defaults to False.
201+
containers (files and folders inside Projects/Folders). This must be set to
202+
True for recursive to have any effect. Defaults to False.
200203
target_entity_types: Specify which entity types to process when deleting ACLs.
201204
Allowed values are "folder" and "file" (case-insensitive).
202205
If None, defaults to ["folder", "file"].
@@ -234,25 +237,38 @@ def delete_permissions(
234237
syn = Synapse()
235238
syn.login()
236239
237-
# Delete permissions for this folder and all container entities (folders) within it recursively
238-
Folder(id="syn123").delete_permissions(recursive=True)
240+
# Delete permissions for this folder only (does not affect children)
241+
Folder(id="syn123").delete_permissions()
239242
240-
# Delete permissions for all entities within this folder, but not the folder itself
243+
# Delete permissions for all files and folders directly within this folder,
244+
# but not the folder itself
241245
Folder(id="syn123").delete_permissions(
242246
include_self=False,
243247
include_container_content=True
244248
)
245249
250+
# Delete permissions for all items in the entire hierarchy (folders and their files)
251+
# Both recursive and include_container_content must be True
252+
Folder(id="syn123").delete_permissions(
253+
recursive=True,
254+
include_container_content=True
255+
)
256+
246257
# Delete permissions only for folder entities within this folder recursively
258+
# and their contents
247259
Folder(id="syn123").delete_permissions(
248260
recursive=True,
261+
include_container_content=True,
249262
target_entity_types=["folder"]
250263
)
251264
252-
# Delete all permissions in the entire hierarchy
265+
# Delete permissions only for files within this folder and all subfolders
266+
# Both recursive and include_container_content must be True for this to work
253267
Folder(id="syn123").delete_permissions(
268+
include_self=False,
254269
recursive=True,
255-
include_container_content=True
270+
include_container_content=True,
271+
target_entity_types=["file"]
256272
)
257273
```
258274
"""

tests/unit/synapseclient/models/async/unit_test_permissions_async.py

Lines changed: 20 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -165,24 +165,6 @@ async def test_delete_permissions_folder_recursive(self):
165165
entity_id="syn123", synapse_client=self.synapse_client
166166
)
167167

168-
async def test_sync_failure_doesnt_process_children(self):
169-
"""Test that sync failure prevents processing children."""
170-
# GIVEN a folder with an ID and mocked sync_from_synapse_async method that fails
171-
folder = Folder(id="syn123")
172-
folder.sync_from_synapse_async = AsyncMock(side_effect=Exception("Sync failed"))
173-
174-
# WHEN deleting permissions recursively
175-
await folder.delete_permissions_async(recursive=True)
176-
177-
# THEN sync_from_synapse_async should be called
178-
folder.sync_from_synapse_async.assert_called_once()
179-
180-
# AND delete_entity_acl should be called on the folder itself
181-
self.mock_delete_acl.assert_called_once()
182-
183-
# AND a warning log message should be generated
184-
self.synapse_client.logger.warning.assert_called_once()
185-
186168
async def test_filter_by_entity_type_folder(self):
187169
"""Test filtering deletion by folder entity type."""
188170
# GIVEN a folder with an ID and child folder and file
@@ -299,3 +281,23 @@ async def test_general_exception_during_delete(self):
299281

300282
# AND a warning log message should be generated
301283
self.synapse_client.logger.warning.assert_called_once()
284+
285+
async def test_invalid_recursive_without_include_container_content(self):
286+
"""Test that setting recursive=True without include_container_content=True raises ValueError."""
287+
# GIVEN a folder with an ID
288+
folder = Folder(id="syn123")
289+
290+
# WHEN attempting to delete permissions with recursive=True but include_container_content=False
291+
# THEN a ValueError should be raised
292+
with pytest.raises(
293+
ValueError,
294+
match="When recursive=True, include_container_content must also be True",
295+
):
296+
await folder.delete_permissions_async(
297+
recursive=True, include_container_content=False
298+
)
299+
300+
# AND the delete_entity_acl function should still be called for the folder itself
301+
self.mock_delete_acl.assert_called_once_with(
302+
entity_id="syn123", synapse_client=self.synapse_client
303+
)

tests/unit/synapseclient/models/unit_test_permissions.py

Lines changed: 18 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -163,24 +163,6 @@ async def test_delete_permissions_folder_recursive(self):
163163
entity_id="syn123", synapse_client=self.synapse_client
164164
)
165165

166-
async def test_sync_failure_doesnt_process_children(self):
167-
"""Test that sync failure prevents processing children."""
168-
# GIVEN a folder with an ID and mocked sync_from_synapse_async method that fails
169-
folder = Folder(id="syn123")
170-
folder.sync_from_synapse_async = AsyncMock(side_effect=Exception("Sync failed"))
171-
172-
# WHEN deleting permissions recursively
173-
folder.delete_permissions(recursive=True)
174-
175-
# THEN sync_from_synapse_async should be called
176-
folder.sync_from_synapse_async.assert_called_once()
177-
178-
# AND delete_entity_acl should be called on the folder itself
179-
self.mock_delete_acl.assert_called_once()
180-
181-
# AND a warning log message should be generated
182-
self.synapse_client.logger.warning.assert_called_once()
183-
184166
async def test_filter_by_entity_type_folder(self):
185167
"""Test filtering deletion by folder entity type."""
186168
# GIVEN a folder with an ID and child folder and file
@@ -297,3 +279,21 @@ async def test_general_exception_during_delete(self):
297279

298280
# AND a warning log message should be generated
299281
self.synapse_client.logger.warning.assert_called_once()
282+
283+
async def test_invalid_recursive_without_include_container_content(self):
284+
"""Test that setting recursive=True without include_container_content=True raises ValueError."""
285+
# GIVEN a folder with an ID
286+
folder = Folder(id="syn123")
287+
288+
# WHEN attempting to delete permissions with recursive=True but include_container_content=False
289+
# THEN a ValueError should be raised
290+
with pytest.raises(
291+
ValueError,
292+
match="When recursive=True, include_container_content must also be True",
293+
):
294+
folder.delete_permissions(recursive=True, include_container_content=False)
295+
296+
# AND the delete_entity_acl function should still be called for the folder itself
297+
self.mock_delete_acl.assert_called_once_with(
298+
entity_id="syn123", synapse_client=self.synapse_client
299+
)

0 commit comments

Comments
 (0)