Skip to content
Merged
1 change: 1 addition & 0 deletions docs/reference/experimental/async/wiki.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
- restore_async
- get_async
- delete_async
- copy_async
- get_attachment_handles_async
- get_attachment_async
- get_attachment_preview_async
Expand Down
1 change: 1 addition & 0 deletions docs/reference/experimental/sync/wiki.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
- restore
- get
- delete
- copy
- get_attachment_handles
- get_attachment
- get_attachment_preview
Expand Down
2 changes: 2 additions & 0 deletions synapseclient/api/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,7 @@
post_external_filehandle,
post_external_object_store_filehandle,
post_external_s3_file_handle,
post_file_handles_copy,
post_file_multipart,
post_file_multipart_presigned_urls,
put_file_multipart_add,
Expand Down Expand Up @@ -217,6 +218,7 @@
"get_file_handle",
"get_file_handle_presigned_url",
"post_external_filehandle",
"post_file_handles_copy",
"post_file_multipart_presigned_urls",
"put_file_multipart_add",
"AddPartResponse",
Expand Down
60 changes: 60 additions & 0 deletions synapseclient/api/file_services.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
<https://rest-docs.synapse.org/rest/#org.sagebionetworks.repo.web.controller.EntityController>
"""

import asyncio
import json
import mimetypes
import os
Expand Down Expand Up @@ -544,3 +545,62 @@ def get_file_handle_for_download(
f"associated with the Synapse {entity_type}: {synapse_id}"
)
return result


async def post_file_handles_copy(
copy_requests: list[dict[str, Any]],
*,
synapse_client: Optional["Synapse"] = None,
) -> list[dict[str, Any]]:
"""
Copy a batch of file handles. Requests are automatically split into batches of
MAX_FILE_HANDLE_PER_COPY_REQUEST, and the batches are submitted concurrently,
with at most max_threads requests in flight at a time.

<https://rest-docs.synapse.org/rest/POST/filehandles/copy.html>

Arguments:
copy_requests: A list of copy requests, each matching
<https://rest-docs.synapse.org/rest/org/sagebionetworks/repo/model/file/FileHandleCopyRequest.html>
synapse_client: If not passed in and caching was not disabled by
`Synapse.allow_client_caching(False)` this will use the last created
instance from the Synapse class constructor.

Returns:
A list of copy results matching
<https://rest-docs.synapse.org/rest/org/sagebionetworks/repo/model/file/FileHandleCopyResult.html>,
in the same order as copy_requests.
Failed copies include a failureCode of UNAUTHORIZED or NOT_FOUND.
"""
from synapseclient import Synapse
from synapseclient.core.constants.limits import MAX_FILE_HANDLE_PER_COPY_REQUEST

client = Synapse.get_client(synapse_client=synapse_client)

if not copy_requests:
return []

semaphore = asyncio.Semaphore(client.max_threads)

async def copy_batch(batch: list[dict[str, Any]]) -> list[dict[str, Any]]:
async with semaphore:
response = await client.rest_post_async(
"/filehandles/copy",
body=json.dumps({"copyRequests": batch}),
endpoint=client.fileHandleEndpoint,
)
return response.get("copyResults", [])

tasks = [
asyncio.create_task(
copy_batch(copy_requests[start : start + MAX_FILE_HANDLE_PER_COPY_REQUEST])
)
for start in range(0, len(copy_requests), MAX_FILE_HANDLE_PER_COPY_REQUEST)
]

batched_results = await asyncio.gather(*tasks)

copy_results = []
for batch_results in batched_results:
copy_results.extend(batch_results)
return copy_results
64 changes: 64 additions & 0 deletions synapseclient/models/protocols/wikipage_protocol.py
Original file line number Diff line number Diff line change
Expand Up @@ -340,6 +340,70 @@ def get_attachment_preview(
"""
return ""

def copy(
self,
destination_owner_id: str,
destination_sub_page_id: Optional[str] = None,
update_links: bool = True,
entity_map: Optional[Dict[str, str]] = None,
*,
synapse_client: Optional["Synapse"] = None,
) -> List["WikiHeader"]:
"""
Copy the wiki page tree of the owner entity to another entity and
update internal links.

If id is set on this WikiPage, only the sub-tree rooted at that wiki page
is copied. Otherwise the entire wiki of the owner entity is copied.

Arguments:
destination_owner_id: The Synapse ID of the entity that the wiki
will be copied to.
destination_sub_page_id: Optional ID of a wiki page that already
exists in the destination. The root of the copied tree is written
into that page, replacing its title, markdown, and attachments,
and the rest of the copied pages are created beneath it.
update_links: Update all the internal links so that they point at the
copied wiki pages. For example, syn1234/wiki/34345 becomes
syn3345/wiki/49508. Defaults to True.
entity_map: A mapping of old Synapse IDs to new Synapse IDs, for
example {"syn1234": "syn2345"}. If provided, the Synapse IDs
referenced in the markdown of the copied wiki pages are updated,
for example syn1234 becomes syn2345. If omitted, Synapse IDs
are left unchanged.
synapse_client: If not passed in and caching was not disabled by
Synapse.allow_client_caching(False) this will use the last created
instance from the Synapse class constructor.
Returns:
A list of WikiHeader objects for the destination entity.

Example: Copy the entire wiki of an entity to another entity
This example shows how to copy all wiki pages from one project to another.
```python
from synapseclient import Synapse
from synapseclient.models import WikiPage

syn = Synapse()
syn.login()

new_wiki_headers = WikiPage(owner_id="syn123").copy(
destination_owner_id="syn456"
)
print(new_wiki_headers)
```
Example: Copy a wiki sub-tree and update Synapse ID references
This example shows how to copy a specific wiki page and its sub-pages,
rewriting references to syn1234 so they point at syn2345.
```python
new_wiki_headers = WikiPage(owner_id="syn123", id="34345").copy(
destination_owner_id="syn456",
entity_map={"syn1234": "syn2345"},
)
print(new_wiki_headers)
```
"""
return []

def get_markdown_file(
self,
*,
Expand Down
Loading
Loading