"""Pull and push wiki tools"""
import json
import os
import typing
import synapseclient
from synapseclient import Synapse
[docs]def pull_wiki(syn: Synapse, project: str, workdir: str = "./") -> typing.List[dict]:
"""Downloads each wikipage's content into a markdown file and
stores a configuration file
Args:
syn: Synapse connection
project: synapseclient.Project or its id
workdir: Location to download markdown files and wiki_config.json
into. Defaults to location of where code is being
executed.
Returns:
Wiki Configuration::
[
{
"id": "111",
"title": "homepage",
"markdown_path": "111-homepage.md"
},
{...}
]
"""
projectid = synapseclient.core.utils.id_of(project)
wiki_headers = syn.getWikiHeaders(projectid)
for wiki_header in wiki_headers:
wiki = syn.getWiki(projectid, subpageId=wiki_header["id"])
# Convert all special characters to underscore
# This way markdown paths don't have special characters
# and json file can be written without encoding
clean_title = "".join(letter for letter in wiki["title"] if letter.isalnum())
# Home page title is always blank
if clean_title == "":
clean_title = "homepage"
wiki_header["title"] = clean_title
# The wiki id is added to the markdown path because wiki ids are
# unique, but wiki titles don't have to be
markdown_path = os.path.join(workdir, f"{wiki.id}-{clean_title}.md")
with open(markdown_path, "w") as md_file:
md_file.write(wiki["markdown"])
wiki_header["markdown_path"] = f"{wiki.id}-{clean_title}.md"
return wiki_headers
[docs]def read_wiki_config(workdir: str) -> typing.List[dict]:
"""Read wiki config file from working directory
Args:
workdir: Working directory
Returns:
Wiki Configuration::
[
{
"id": "111",
"title": "title",
"parentId": "33333",
"markdown_path": "home.md"
},
{...}
]
"""
config_path = os.path.join(workdir, "wiki_config.json")
if not os.path.exists(config_path):
raise ValueError("wiki_config.json must exist at specified workdir")
with open(config_path, "r") as config:
wiki_config = json.load(config)
return wiki_config
[docs]def validate_config(workdir: str) -> typing.List[dict]:
"""Validates wiki configuration
Args:
workdir: Workfing directory with markdown and wiki_config.json
Returns:
Wiki Configuration::
[
{
"id": "111",
"title": "title",
"parentId": "33333",
"markdown_path": "home.md"
},
{...}
]
Raises:
ValueError:
`markdown_path` is specified but cannot be located.
There are duplicated wiki ids.
`id` is not specified and (`markdown_path`/`parentId`/`title`
is missing or `parentId` not one of the `id`s.)
`id` cannot be the same as `parentId`.
"""
wiki_config = read_wiki_config(workdir)
ids = [
str(wiki_header["id"])
for wiki_header in wiki_config
if wiki_header.get("id") is not None
]
if len(set(ids)) != len(ids):
raise ValueError("Must have unique ids.")
# Must have one home page where `parentId` is blank
home_page = [wiki_header.get("parentId") is None for wiki_header in wiki_config]
if sum(home_page) != 1:
raise ValueError("Must only have one config " "where `parentId` is blank")
for wiki_header in wiki_config:
# Configuration must not be empty
markdown_path = wiki_header.get("markdown_path")
wikiid = wiki_header.get("id")
parentid = wiki_header.get("parentId")
title = wiki_header.get("title")
# Title must be specified
if title is None:
raise ValueError("Must have title")
title = str(title).strip()
# Make id and parentid strings and strip white spaces if values
# are specified
if wikiid is not None:
wikiid = str(wikiid).strip()
wiki_header["id"] = wikiid
if parentid is not None:
parentid = str(parentid).strip()
wiki_header["parentId"] = parentid
# id and parentid must not be empty strings
if wikiid == "" or parentid == "" or title == "":
raise ValueError(
"`id`, `parentId`, and `title` must not be "
"empty strings if specified"
)
# Markdown file must exist if specified
if markdown_path is not None and not os.path.isfile(
os.path.join(workdir, markdown_path)
):
raise ValueError(f"{markdown_path} does not exist")
# id must not be the same as parentid, but if both aren't specified,
# This error shouldn't be raised
if wikiid is not None and parentid is not None and wikiid == parentid:
raise ValueError("`id` and `parentId` can't be equal.")
# If parentid is specified, it has to be in one of the `ids`
if parentid is not None and parentid not in ids:
raise ValueError("`parentId` must be one of the " "`id`s in the config")
return wiki_config
[docs]def push_wiki(syn: Synapse, projectid: str, workdir: str = "./") -> typing.List[dict]:
"""Pushes Wiki from configuration
Args:
syn: Synapse connection
project: synapseclient.Project or its id
workdir: Location to download markdown files and wiki_config.json.
Defaults to location of where code is being
executed.
Returns:
Wiki Configuration::
[
{
"id": "111",
"title": "title",
"parentId": "33333",
"markdown_path": "home.md"
},
{...}
]
"""
wiki_config = validate_config(workdir)
for wiki_header in wiki_config:
# no markdown path, nothing to update
markdown_path = wiki_header.get("markdown_path")
if not wiki_header.get("markdown_path"):
print(f"Markdown not specified: {wiki_header['title']}")
continue
markdown_path = os.path.join(workdir, markdown_path)
with open(markdown_path, "r") as md_f:
markdown = md_f.read()
# Create new wiki page if id isn't specified
if wiki_header.get("id") is not None:
wiki = syn.getWiki(projectid, subpageId=wiki_header["id"])
# Don't store if the wiki pages are the same
if wiki.markdown == markdown:
print(f"no updates: {wiki_header['title']}")
continue
print(f"Wiki updated: {wiki_header['title']}")
else:
wiki = synapseclient.Wiki(
owner=projectid,
title=wiki_header["title"],
parentWikiId=wiki_header["parentId"],
)
print(f"Wiki added: {wiki_header['title']}")
wiki.markdown = markdown
wiki = syn.store(wiki)
# If new wiki page is added, must add to wiki_config.json
wiki_header["id"] = wiki["id"]
return wiki_config