Skip to content

[SYNPY-1351, SYNPY-1613] Implement 'Wiki2' model into OOP #1206

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 42 commits into
base: develop
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 24 commits
Commits
Show all changes
42 commits
Select commit Hold shift + click to select a range
fef882b
add Low-level Functionality to interact with wikipage2 endpoints
danlu1 Jun 11, 2025
977413b
remove unused module
danlu1 Jun 11, 2025
b2a5840
resort
danlu1 Jun 11, 2025
1ce73fd
add synchronous protocol parent class
danlu1 Jun 11, 2025
0685de4
add synchronous protocol for wiki2
danlu1 Jun 11, 2025
0fc3f75
remove redundant docstring
danlu1 Jun 11, 2025
d4a98e8
all pre-signed url to be downloaded directly
danlu1 Jun 13, 2025
86fa041
add rest_get_paginated_async to get paginated results from api call
danlu1 Jun 13, 2025
6601fdc
add unit test for rest_get_paginated_async
danlu1 Jun 16, 2025
b7289f2
add unit test for rest_get_paginated_async
danlu1 Jun 16, 2025
21a4d8d
tweak test cases
danlu1 Jun 16, 2025
e06a35a
use rest_get_paginated_async to get paginated results
danlu1 Jun 16, 2025
2fdfc71
reformat download_functions.py
danlu1 Jun 16, 2025
176cdf8
use specified destination for pre-signed url downloads
danlu1 Jun 17, 2025
c52996f
remove unused modules
danlu1 Jun 23, 2025
99cbb81
update function names
danlu1 Jun 23, 2025
47f38e0
define optional params
danlu1 Jun 23, 2025
a620db5
update function names
danlu1 Jun 23, 2025
4e89258
add tutorials
danlu1 Jun 23, 2025
da4dfa4
remove unwanted module name
danlu1 Jun 23, 2025
be5aae4
update typing hint
danlu1 Jun 23, 2025
1ab9888
make sure gzip file always be removed after getting the file handle id
danlu1 Jun 23, 2025
082df85
update filename for markdown gzip file
danlu1 Jun 24, 2025
43bb9a0
update tutorials
danlu1 Jun 24, 2025
0a5f2e6
update markdown name if it's created from text to make it more inform…
danlu1 Jun 24, 2025
c6077e0
reorder functions for order hint
danlu1 Jun 24, 2025
cef775b
update wikipage name and instructions
danlu1 Jun 24, 2025
e32da1b
reorder and fill in details for tutorial md
danlu1 Jun 24, 2025
50417a3
remove unwanted comments
danlu1 Jun 24, 2025
d59a9d6
make low-level functionalities importable from synapseclient.api
danlu1 Jun 25, 2025
a317bc9
rename post_wiki to post_wiki_page
danlu1 Jun 25, 2025
9ae4c81
remove debug code
danlu1 Jun 25, 2025
d52720c
remove unwanted decorator
danlu1 Jun 27, 2025
cbcbf35
refactor wikipage store function to put validation at the beginning a…
danlu1 Jun 27, 2025
5c66880
remove debug statement
danlu1 Jun 30, 2025
d8055d6
simplify get_async for wikipage
danlu1 Jun 30, 2025
9d923ff
remove redirect and reorganize args and kwargs
danlu1 Jul 3, 2025
b362230
set default values for get_wiki_history
danlu1 Jul 9, 2025
02b509a
add async and sync unit test for wiki model
danlu1 Jul 9, 2025
62f03d4
remove redirect params
danlu1 Jul 9, 2025
e56540e
update presigned url provider params for multithread downloader
danlu1 Jul 9, 2025
a46417c
remove duplicate expired presigned url checking
danlu1 Jul 14, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
244 changes: 244 additions & 0 deletions docs/tutorials/python/tutorial_scripts/wiki.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,244 @@
#!/usr/bin/env python3
"""
Tutorial script demonstrating the Synapse Wiki models functionality.

This script shows how to:
1. Create, read, and update wiki pages
2. Work with WikiPage Markdown
Copy link
Member

@BryanFauble BryanFauble Jun 25, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there another descriptive way we can word some of these actions rather than "Work with.." maybe, describing the create/update/delete/restore actions taking place during that section of code?

3. Work with WikiPage Attachments
4. Work with WikiHeader
5. Work with WikiHistorySnapshot
6. Work with WikiOrderHint
7. Delete wiki pages
"""
import gzip
import os
import uuid

from synapseclient import Synapse
from synapseclient.models import (
Project,
WikiHeader,
WikiHistorySnapshot,
WikiOrderHint,
WikiPage,
)

# Initialize Synapse client
syn = Synapse()
syn.login()

# Create a Synapse Project to work with
my_test_project = Project(
name=f"My Test Project_{uuid.uuid4()}",
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Check out the other tutorials created. There is a theme around a project for alzheimer's disease that i had been following.

The tutorial is great, and covers everything one would care to do. Let's see if we can tweak the theme of the content just a bit.

description="This is a test project for the wiki tutorial.",
).store()
print(f"Created project: {my_test_project.name} with ID: {my_test_project.id}")

# Section1: Create, read, and update wiki pages
# Create a new wiki page for the project with plain text markdown
wiki_page_1 = WikiPage(
owner_id=my_test_project.id,
title="My Root Wiki Page",
markdown="# Welcome to My Root Wiki\n\nThis is a sample root wiki page created with the Synapse client.",
).store()

# OR you can create a wiki page with an existing markdown file
markdown_file_path = "path/to/your_markdown_file.md"
wiki_page_1 = WikiPage(
owner_id=my_test_project.id,
title="My First Root Wiki Page Version with existing markdown file",
markdown=markdown_file_path,
).store()

# Create a new wiki page with updated content
wiki_page_2 = WikiPage(
owner_id=my_test_project.id,
title="My First Root Wiki Page Version 1",
markdown="# Welcome to My Root Wiki Version 1\n\nThis is a sample root wiki page created with the Synapse client.",
id=wiki_page_1.id,
).store()

# Restore the wiki page to the original version
wiki_page_restored = WikiPage(
owner_id=my_test_project.id, id=wiki_page_1.id, wiki_version="0"
).restore()

# check if the content is restored
comparisons = [
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is probably easier and cleaner to use assert statements here. Like

assert condition, "Message for failure"

That was if it does fail, it's clear which one failed the compare check

wiki_page_1.markdown_file_handle_id == wiki_page_restored.markdown_file_handle_id,
wiki_page_1.id == wiki_page_restored.id,
wiki_page_1.title == wiki_page_restored.title,
]
print(f"All fields match: {all(comparisons)}")

# Create a sub-wiki page
sub_wiki = WikiPage(
owner_id=my_test_project.id,
title="Sub Wiki Page 1",
parent_id=wiki_page_1.id, # Use the ID of the parent wiki page we created '633033'
markdown="# Sub Page 1\n\nThis is a sub-page of another wiki.",
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Out of curiosity. If you use a multi-line python string:

multi_line_string = """
Content
On
This
Line
"""

Does that automatically add in the newline characters? Or are you forced to use the \n characters?

).store()

# Get an existing wiki page for the project, now you can see one root wiki page and one sub-wiki page
wiki_header_tree = WikiHeader.get(owner_id=my_test_project.id)
print(wiki_header_tree)

# Once you know the wiki page id, you can retrieve the wiki page with the id
retrieved_wiki = WikiPage(owner_id=my_test_project.id, id=sub_wiki.id).get()
print(f"Retrieved wiki page with title: {retrieved_wiki.title}")

# Or you can retrieve the wiki page with the title
retrieved_wiki = WikiPage(owner_id=my_test_project.id, title=wiki_page_1.title).get()
print(f"Retrieved wiki page with title: {retrieved_wiki.title}")

# Check if the retrieved wiki page is the same as the original wiki page
comparisons = [
wiki_page_1.markdown_file_handle_id == retrieved_wiki.markdown_file_handle_id,
wiki_page_1.id == retrieved_wiki.id,
wiki_page_1.title == retrieved_wiki.title,
]
print(f"All fields match: {all(comparisons)}")

# Section 2: WikiPage Markdown Operations
# Create wiki page from markdown text
markdown_content = """# Sample Markdown Content

## Section 1
This is a sample markdown file with multiple sections.

## Section 2
- List item 1
- List item 2
- List item 3

## Code Example
```python
def hello_world():
print("Hello, World!")
```
"""

# Create wiki page from markdown text
markdown_wiki_1 = WikiPage(
owner_id=my_test_project.id,
parent_id=wiki_page_1.id,
title="Sub Page 2 created from markdown text",
markdown=markdown_content,
).store()

# Create a wiki page from a markdown file
# Create a temporary markdown gzipped file from the markdown_content
markdown_file_path = "temp_markdown_file.md.gz"
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In most of the tutorials we have been using the "~/temp` directory to write files to

with gzip.open(markdown_file_path, "wt", encoding="utf-8") as gz:
gz.write("This is a markdown file")

# Create wiki page from markdown file
markdown_wiki_2 = WikiPage(
owner_id=my_test_project.id,
parent_id=wiki_page_1.id,
title="Sub Page 3 created from markdown file",
markdown=markdown_file_path,
).store()

# Download the markdown file
# delete the markdown file after downloading
os.remove(markdown_file_path)
markdown_file_2 = WikiPage(
owner_id=my_test_project.id, id=markdown_wiki_2.id
).get_markdown(download_file=True, download_location=".")

print(f"Markdown file downloaded to: {markdown_file_2}")

# Section 3: WikiPage with Attachments
# Create a temporary file for the attachment
attachment_file_name = "temp_attachment.txt.gz"
with gzip.open(attachment_file_name, "wt", encoding="utf-8") as gz:
gz.write("This is a sample attachment.")

# reformat the attachment file name to be a valid attachment path
attachment_file_name_reformatted = attachment_file_name.replace(".", "%2E")
attachment_file_name_reformatted = attachment_file_name_reformatted.replace("_", "%5F")

wiki_with_attachments = WikiPage(
owner_id=my_test_project.id,
parent_id=wiki_page_1.id,
title="Sub Page 4 with Attachments",
markdown=f"# Sub Page 4 with Attachments\n\nThis is a attachment: ${{previewattachment?fileName={attachment_file_name_reformatted}}}",
attachments=[attachment_file_name],
).store()

# Get attachment handles
attachment_handles = WikiPage(
owner_id=my_test_project.id, id=wiki_with_attachments.id
).get_attachment_handles()
print(f"Found {len(attachment_handles['list'])} attachments")

# Delete the attachment file after uploading --> check if the file is deleted
os.remove(attachment_file_name)
# Download an attachment
wiki_page = WikiPage(
owner_id=my_test_project.id, id=wiki_with_attachments.id
).get_attachment(
file_name=attachment_file_name,
download_file=True,
download_location=".",
)
print(f"Attachment downloaded: {os.path.exists(attachment_file_name)}")

# Get attachment URL without downloading
wiki_page_url = WikiPage(
owner_id=my_test_project.id, id=wiki_with_attachments.id
).get_attachment(
file_name="temp_attachment.txt.gz",
download_file=False,
)
print(f"Attachment URL: {wiki_page_url}")

# Download an attachment preview--? Failed to download the attachment preview, synapseclient.core.exceptions.SynapseHTTPError: 404 Client Error: Cannot find a wiki attachment for OwnerID: syn68493645, ObjectType: ENTITY, WikiPageId: 633100, fileName: preview.txt
attachment_handles = WikiPage(
owner_id=my_test_project.id, id=wiki_with_attachments.id
).get_attachment_handles()
print(f"Attachment handles: {attachment_handles}")
wiki_page = WikiPage(
owner_id=my_test_project.id, id=wiki_with_attachments.id
).get_attachment_preview(
file_name="preview.txt",
download_file=True,
download_location=".",
)

# Section 4: WikiHeader - Working with Wiki Hierarchy

# Get wiki header tree (hierarchy)
# Note: Uncomment to actually get the header tree
headers = WikiHeader.get(owner_id=my_test_project.id)
print(f"Found {len(headers)} wiki pages in hierarchy")

# Section 5. WikiHistorySnapshot - Version History
# Get wiki history
history = WikiHistorySnapshot.get(owner_id=my_test_project.id, id=wiki_page_1.id)

print(f"Found {len(history)} versions in history for {wiki_page_1.title}")

# Section 6. WikiOrderHint - Ordering Wiki Pages
# Get wiki order hint --> failed to get the order hint
order_hint = WikiOrderHint(owner_id=my_test_project.id).get()
print(f"Order hint for {my_test_project.id}: {order_hint}")

# Update wiki order hint
order_hint.id_list = [wiki_page_1.id]

print(f"Created order hint for {len(order_hint.id_list)} wiki pages")

# Update order hint
order_hint.id_list = ["633084", "633085", "633086", "633087", "633088"] # Reorder
order_hint.store()

# Delete a wiki page
wiki_page_to_delete = WikiPage(
owner_id=my_test_project.id, id=wiki_with_attachments.id
).delete()

# clean up
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would leave the project delete call out of the tutorial.

For the wiki page that is being deleted, I might consider making a new wiki page specifically to delete, rather than delete one of the ones used earlier in the tutorial

my_test_project.delete()
152 changes: 151 additions & 1 deletion docs/tutorials/python/wiki.md
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fantastic tutorial!

Original file line number Diff line number Diff line change
@@ -1,2 +1,152 @@
# Wikis on Projects
![Under Construction](../../assets/under_construction.png)

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Now that this tutorial exists it will need to be added to the page navigation in the mkdocs.yml file

# Synapse Wiki Models Tutorial

This tutorial demonstrates how to work with Wiki models in the Synapse Python client. Wikis in Synapse provide a way to create rich documentation and collaborative content for projects, folders, files, datasets, and other entities.

## Overview

The Synapse Wiki models include:
- **WikiPage**: The main wiki page model for creating and managing wiki content
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could these also hyperlink to the relevant api doc pages for each model?

- **WikiHeader**: Represents wiki page headers and hierarchy information
- **WikiHistorySnapshot**: Provides access to wiki version history
- **WikiOrderHint**: Manages the order of wiki pages within an entity

This tutorial shows how to:
1. Create, read, and update wiki pages
2. Work with WikiPage Markdown
3. Work with WikiPage Attachments
4. Work with WikiHeader
5. Work with WikiHistorySnapshot
6. Work with WikiOrderHint
7. Delete wiki pages

## Basic Setup
```python
{!docs/tutorials/python/tutorial_scripts/wiki.py!lines=14-29}
```

## 1. Create, read, and update wiki pages
### Create a new wiki page for the project with plain text markdown
```python
{!docs/tutorials/python/tutorial_scripts/wiki.py!lines=33-37}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Iine 32 is missing, that is where the project variable is created

```

### OR you can create a wiki page with an existing markdown file
```python
{!docs/tutorials/python/tutorial_scripts/wiki.py!lines=40-45}
```

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A great addition would be to include a section that can be expanded to show what the console logs would be after running a particular step.

There are some examples on other tutorials that can be followed

### Create a new wiki page with updated content
```python
{!docs/tutorials/python/tutorial_scripts/wiki.py!lines=48-53}
```

### Restore the wiki page to the original version
```python
{!docs/tutorials/python/tutorial_scripts/wiki.py!lines=56-64}
```

### Create a sub-wiki page
```python
{!docs/tutorials/python/tutorial_scripts/wiki.py!lines=67-72}
```

### Get an existing wiki page for the project, now you can see one root wiki page and one sub-wiki page
```python
{!docs/tutorials/python/tutorial_scripts/wiki.py!lines=75-76}
```

### Retrieving a Wiki Page
Note: You need to know the wiki page ID or wiki page title to retrieve it
#### Retrieve a Wiki Page with wiki page ID
```python
{!docs/tutorials/python/tutorial_scripts/wiki.py!lines=79-80}
```

#### Retrieve a Wiki Page with wiki page title
```python
{!docs/tutorials/python/tutorial_scripts/wiki.py!lines=83-84}
```

#### Check if the retrieved wiki page is the same as the original wiki page
```python
{!docs/tutorials/python/tutorial_scripts/wiki.py!lines=87-92}
```

## 2. WikiPage Markdown Operations
### Create wiki page from markdown text
```python
{!docs/tutorials/python/tutorial_scripts/wiki.py!lines=96-119}
```

### Create wiki page from a markdown file
```python
{!docs/tutorials/python/tutorial_scripts/wiki.py!lines=123-133}
```

### Download the markdown file
Note: If the markdown is generated from plain text using the client, the downloaded file will be named wiki_markdown_<wiki_page_id>.md.gz. If it is generated from an existing markdown file, the downloaded file will retain the original filename with the .gz suffix appended.
```python
{!docs/tutorials/python/tutorial_scripts/wiki.py!lines=137-143}
```

## 3. WikiPage Attachments Operations
### Create a wiki page with attachments
```python
{!docs/tutorials/python/tutorial_scripts/wiki.py!lines=147-161}
```
### Get the file handles of all attachments on this wiki page.
```python
{!docs/tutorials/python/tutorial_scripts/wiki.py!lines=164-165}
```
### Download an attachment
```python
{!docs/tutorials/python/tutorial_scripts/wiki.py!lines=167-175}
```

### Get attachment URL without downloading
```python
{!docs/tutorials/python/tutorial_scripts/wiki.py!lines=178-182}
```

### Download an attachment preview (WIP)
```python
{!docs/tutorials/python/tutorial_scripts/wiki.py!lines=185-191}
```
#### Get attachment preview URL without downloading (WIP)


## 4. WikiHeader - Working with Wiki Hierarchy
### Getting Wiki Header Tree
```python
{!docs/tutorials/python/tutorial_scripts/wiki.py!lines=197-200}
```

## 5. WikiHistorySnapshot - Version History

### Accessing Wiki History
```python
{!docs/tutorials/python/tutorial_scripts/wiki.py!lines=204-208}
```

## 6. WikiOrderHint - Managing Wiki Order
### Get wiki order hint (No id_list returned, same result getting from direct endpoint calls)
```python
{!docs/tutorials/python/tutorial_scripts/wiki.py!lines=212-213}
```
### Update wiki order hint
```python
{!docs/tutorials/python/tutorial_scripts/wiki.py!lines=216-222}
```

### Deleting a Wiki Page
Note: You need to know the owner ID and wiki page ID to delete a wiki page
```python
{!docs/tutorials/python/tutorial_scripts/wiki.py!lines=225}
```

## clean up
```python
{!docs/tutorials/python/tutorial_scripts/wiki.py!lines=228}
```
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There should be a final references section that links to all the functions and models used in the tutorial

Loading
Loading