Ejemplo n.º 1
0
 def test_copy_file_handles__invalid_input_params_branch1(self):
     file_handles = ["test"]
     obj_types = []
     obj_ids = ["123"]
     with pytest.raises(ValueError):
         synapseutils.copyFileHandles(self.syn, file_handles, obj_types,
                                      obj_ids)
     self.mock_private_copy.assert_not_called()
Ejemplo n.º 2
0
 def test_copy_file_handles__invalid_input_params_branch3(self):
     file_handles = ["test"]
     obj_types = ["FileEntity"]
     obj_ids = ["123"]
     new_con_type = ["text/plain"]
     new_file_name = []
     with pytest.raises(ValueError):
         synapseutils.copyFileHandles(self.syn, file_handles, obj_types,
                                      obj_ids, new_con_type, new_file_name)
     self.mock_private_copy.assert_not_called()
def test_copyFileHandles__copying_cached_file_handles():
    num_files = 3
    file_entities = []

    # upload temp files to synapse
    for i in range(num_files):
        file_path = utils.make_bogus_data_file()
        schedule_for_cleanup(file_path)
        file_entities.append(syn.store(File(file_path, name=str(uuid.uuid1()), parent=project)))

    # a bunch of setup for arguments to the function under test
    file_handles = [file_entity['_file_handle'] for file_entity in file_entities]
    file_entity_ids = [file_entity['id'] for file_entity in file_entities]
    content_types = [file_handle['contentType'] for file_handle in file_handles]
    filenames = [file_handle['fileName'] for file_handle in file_handles]

    # remove every other FileHandle from the cache (at even indicies)
    for i in range(num_files):
        if i % 2 == 0:
            syn.cache.remove(file_handles[i]["id"])

    # get the new list of file_handles
    copiedFileHandles = synapseutils.copyFileHandles(syn, file_handles, ["FileEntity"] * num_files, file_entity_ids,
                                                     content_types, filenames)
    new_file_handle_ids = [copy_result['newFileHandle']['id'] for copy_result in copiedFileHandles['copyResults']]

    # verify that the cached paths are the same
    for i in range(num_files):
        original_path = syn.cache.get(file_handles[i]['id'])
        new_path = syn.cache.get(new_file_handle_ids[i])
        if i % 2 == 0:  # since even indicies are not cached, both should be none
            assert_is_none(original_path)
            assert_is_none(new_path)
        else:  # at odd indicies, the file path should have been copied
            assert_equals(original_path, new_path)
def copyFileIdsInBatch(syn,
                       table_id,
                       fileIds,
                       content_type="application/json"):
    """Copy file handles from a pandas.Series object.

    Parameters
    ----------
    syn : synapseclient.Synapse
    table_id : str
        Synapse ID of the table the original file handles belong to.
    fileIds : pandas.Series
        The column containing file handles
    content_type : str

    Returns
    -------
    A dict mapping original file handles to newly created file handles.
    """
    fhids_to_copy = fileIds.dropna().drop_duplicates().astype(int).tolist()
    new_fhids = []
    for i in range(0, len(fhids_to_copy), 100):
        fhids_to_copy_i = fhids_to_copy[i:i + 100]
        new_fhids_i = su.copyFileHandles(
            syn=syn,
            fileHandles=fhids_to_copy_i,
            associateObjectTypes=["TableEntity"] * len(fhids_to_copy_i),
            associateObjectIds=[table_id] * len(fhids_to_copy_i),
            newContentTypes=[content_type] * len(fhids_to_copy_i),
            newFileNames=[None] * len(fhids_to_copy_i))
        for j in [int(i['newFileHandle']['id']) for i in new_fhids_i]:
            new_fhids.append(j)
    fhid_map = {k: v for k, v in zip(fhids_to_copy, new_fhids)}
    return fhid_map
Ejemplo n.º 5
0
def test_copyFileHandles__copying_cached_file_handles():
    num_files = 3
    file_entities = []

    #upload temp files to synapse
    for i in range(num_files):
        file_path = utils.make_bogus_data_file();
        schedule_for_cleanup(file_path)
        file_entities.append(syn.store(File(file_path,name=str(uuid.uuid1()), parent=project)))

    #a bunch of setup for arguments to the function under test
    file_handles = [file_entity['_file_handle'] for file_entity in file_entities ]
    file_entity_ids = [file_entity['id'] for file_entity in file_entities]
    content_types = [file_handle['contentType'] for file_handle in file_handles]
    filenames = [file_handle['fileName'] for file_handle in file_handles]

    #remove every other FileHandle from the cache (at even indicies)
    for i in range(num_files):
        if i % 2 == 0:
            syn.cache.remove(file_handles[i]["id"])

    #get the new list of file_handles
    copiedFileHandles = synapseutils.copyFileHandles(syn, file_handles , ["FileEntity"] * num_files, file_entity_ids,content_types , filenames)
    new_file_handle_ids = [copy_result['newFileHandle']['id'] for copy_result in copiedFileHandles['copyResults']]

    #verify that the cached paths are the same
    for i in range(num_files):
        original_path = syn.cache.get(file_handles[i]['id'])
        new_path = syn.cache.get(new_file_handle_ids[i])
        if i % 2 == 0: # since even indicies are not cached, both should be none
            assert_is_none(original_path)
            assert_is_none(new_path)
        else: # at odd indicies, the file path should have been copied
            assert_equals(original_path, new_path)
Ejemplo n.º 6
0
def curate_raw_data(syn):
    raw_data_folders = [
        SHIMMER_BACK, SHIMMER_LEFT_ANKLE, SHIMMER_LEFT_WRIST,
        SHIMMER_RIGHT_ANKLE, SHIMMER_RIGHT_WRIST
    ]
    raw_data_locations = [
        "Back", "LeftAnkle", "LeftWrist", "RightAnkle", "RightWrist"
    ]
    data_cols = [
        "subject_id", "device", "device_position", "participant_day",
        "timestamp_start", "timestamp_end", "source_file",
        "data_file_handle_id"
    ]
    raw_data = pd.DataFrame(columns=data_cols)
    for folder, device_location in zip(raw_data_folders, raw_data_locations):
        w = su.walk(syn, folder)
        parent, folders, _ = next(w)
        records = []
        for folder_name, folder_id in folders:
            subject, _, subject_files = next(w)
            subject_num = int(re.search("\d+", folder_name).group())
            subject_loc = "BOS"
            subject_id = "{}_{}".format(subject_num, subject_loc)
            for file_name, file_id in subject_files:
                file_day = int(re.search("\d+", file_name).group())
                syn_file = syn.get(file_id)
                df = pd.read_table(syn_file.path)
                timestamp_start = min(df.timestamp)
                timestamp_end = max(df.timestamp)
                fhid = syn_file['dataFileHandleId']
                records.append([
                    subject_id, "Shimmer", device_location, file_day,
                    timestamp_start, timestamp_end, file_id, fhid
                ])
        raw_data_table = pd.DataFrame(records, columns=data_cols)
        fhids_to_copy = raw_data_table['data_file_handle_id'].tolist()
        source_files = raw_data_table["source_file"].tolist()
        new_fhids = []
        for i in range(0, len(fhids_to_copy), 100):
            fhids_subset = fhids_to_copy[i:i + 100]
            source_files_subset = source_files[i:i + 100]
            new_fhids_subset = su.copyFileHandles(
                syn=syn,
                fileHandles=fhids_subset,
                associateObjectTypes=["FileEntity"] * len(fhids_subset),
                associateObjectIds=source_files_subset,
                contentTypes=["text/tab-separated-values"] * len(fhids_subset),
                fileNames=[None] * len(fhids_subset))
            new_fhids_subset = [
                int(i['newFileHandle']['id'])
                for i in new_fhids_subset['copyResults']
            ]
            new_fhids = new_fhids + new_fhids_subset
        fhid_mapping = {k: v for k, v in zip(fhids_to_copy, new_fhids)}
        raw_data_table["data_file_handle_id"] = \
                raw_data_table["data_file_handle_id"].map(fhid_mapping)
        raw_data = raw_data.append(raw_data_table,
                                   ignore_index=True,
                                   sort=False)
    return (raw_data)
Ejemplo n.º 7
0
def test_copyFileHandleAndchangeFileMetadata():
    project_entity = syn.store(Project(name=str(uuid.uuid4())))
    schedule_for_cleanup(project_entity.id)
    filename = utils.make_bogus_data_file()
    attachname = utils.make_bogus_data_file()
    schedule_for_cleanup(filename)
    schedule_for_cleanup(attachname)
    file_entity = syn.store(File(filename, parent=project_entity))
    schedule_for_cleanup(file_entity.id)
    wiki = Wiki(owner=project_entity, title='A Test Wiki', markdown="testing", 
                attachments=[attachname])
    wiki = syn.store(wiki)
    wikiattachments = syn._getFileHandle(wiki.attachmentFileHandleIds[0])
    #CHECK: Can batch copy two file handles (wiki attachments and file entity)
    copiedFileHandles = synapseutils.copyFileHandles(syn, [file_entity.dataFileHandleId, wiki.attachmentFileHandleIds[0]], [file_entity.concreteType.split(".")[-1], "WikiAttachment"], [file_entity.id, wiki.id], [file_entity.contentType, wikiattachments['contentType']], [file_entity.name, wikiattachments['fileName']])
    assert all([results.get("failureCode") is None for results in copiedFileHandles['copyResults']]), "NOT FOUND and UNAUTHORIZED failure codes."

    files = {file_entity.name:{"contentType":file_entity['contentType'],
                               "md5":file_entity['md5']},
             wikiattachments['fileName']:{"contentType":wikiattachments['contentType'],
                                          "md5":wikiattachments['contentMd5']}}
    for results in copiedFileHandles['copyResults']:
        i = results['newFileHandle']
        assert files.get(i['fileName']) is not None, "Filename has to be the same"
        assert files[i['fileName']]['contentType'] == i['contentType'], "Content type has to be the same"
        assert files[i['fileName']]['md5'] == i['contentMd5'], "Md5 has to be the same"

    assert all([results.get("failureCode") is None for results in copiedFileHandles['copyResults']]), "There should not be NOT FOUND and UNAUTHORIZED failure codes."

    if 'username' not in other_user or 'password' not in other_user:
        sys.stderr.write('\nWarning: no test-authentication configured. skipping testing copy function when trying to copy file made by another user.\n')
        return

    syn_other = synapseclient.Synapse(skip_checks=True)
    syn_other.login(other_user['username'], other_user['password'])
    #CHECK: UNAUTHORIZED failure code should be returned
    output = synapseutils.copyFileHandles(syn_other,[file_entity.dataFileHandleId, wiki.attachmentFileHandleIds[0]], [file_entity.concreteType.split(".")[-1], "WikiAttachment"], [file_entity.id, wiki.id], [file_entity.contentType, wikiattachments['contentType']], [file_entity.name, wikiattachments['fileName']])
    assert all([results.get("failureCode") == "UNAUTHORIZED" for results in output['copyResults']]), "UNAUTHORIZED codes."
    #CHECK: Changing content type and downloadAs
    new_entity = synapseutils.changeFileMetaData(syn, file_entity, contentType="application/x-tar", downloadAs="newName.txt")
    schedule_for_cleanup(new_entity.id)
    assert file_entity.md5 == new_entity.md5, "Md5s must be equal after copying"
    fileResult = syn._getFileHandleDownload(new_entity.dataFileHandleId, new_entity.id)
    assert fileResult['fileHandle']['fileName'] == "newName.txt", "Set new file name to be newName.txt"
    assert new_entity.contentType == "application/x-tar", "Set new content type to be application/x-tar"
 def test_copy_file_handles(self):
     # define inputs
     file_handles = [self.file_handle_id_1]
     associate_object_types = ["FileEntity"]
     associate_object_ids = [self.obj_id_1]
     copy_results = synapseutils.copyFileHandles(
         self.syn, file_handles, associate_object_types, associate_object_ids
     )
     # assert copy result contains one copy result
     assert len(copy_results) == 1
Ejemplo n.º 9
0
    def test_copy_file_handles__multiple_batch_calls(self):
        synapseutils.copy_functions.MAX_FILE_HANDLE_PER_COPY_REQUEST = 1  # set batch size to 1
        file_handles = ["789", "NotAccessibleFile"]
        obj_types = ["FileEntity", "FileEntity"]
        obj_ids = ["0987", "2352"]
        con_types = [None, "text/plain"]
        file_names = [None, "testName"]

        expected_calls = [((self.syn, file_handles[0:1], obj_types[0:1],
                            obj_ids[0:1], con_types[0:1], file_names[0:1]), ),
                          ((self.syn, file_handles[1:2], obj_types[1:2],
                            obj_ids[1:2], con_types[1:2], file_names[1:2]), )]

        return_val_1 = [{
            "newFileHandle": {
                "contentMd5": "alpha_num_1",
                "bucketName": "bucket.sagebase.org",
                "fileName": "Name1.txt",
                "createdBy": "111",
                "contentSize": 16,
                "concreteType": "type1",
                "etag": "etag1",
                "id": "0987",
                "storageLocationId": 1,
                "createdOn": "2019-07-24T21:49:40.615Z",
                "contentType": "text/plain",
                "key": "key1"
            },
            "originalFileHandleId": "789"
        }]

        return_val_2 = [{
            "failureCode": "UNAUTHORIZED",
            "originalFileHandleId": "NotAccessibleFile"
        }]

        expected_return = return_val_1 + return_val_2

        self.mock_private_copy.side_effect = [return_val_1, return_val_2
                                              ]  # define multiple returns
        result = synapseutils.copyFileHandles(self.syn, file_handles,
                                              obj_types, obj_ids, con_types,
                                              file_names)
        assert expected_calls == self.mock_private_copy.call_args_list

        assert result == expected_return
        assert self.mock_private_copy.call_count == 2
def test_copyFileHandleAndchangeFileMetadata():
    project_entity = syn.store(Project(name=str(uuid.uuid4())))
    schedule_for_cleanup(project_entity.id)
    filename = utils.make_bogus_data_file()
    attachname = utils.make_bogus_data_file()
    schedule_for_cleanup(filename)
    schedule_for_cleanup(attachname)
    file_entity = syn.store(File(filename, parent=project_entity))
    schedule_for_cleanup(file_entity.id)
    wiki = Wiki(owner=project_entity, title='A Test Wiki', markdown="testing", 
                attachments=[attachname])
    wiki = syn.store(wiki)
    wikiattachments = syn._getFileHandle(wiki.attachmentFileHandleIds[0])
    # CHECK: Can batch copy two file handles (wiki attachments and file entity)
    copiedFileHandles = synapseutils.copyFileHandles(syn, [file_entity.dataFileHandleId,
                                                           wiki.attachmentFileHandleIds[0]],
                                                     [file_entity.concreteType.split(".")[-1], "WikiAttachment"],
                                                     [file_entity.id, wiki.id],
                                                     [file_entity.contentType, wikiattachments['contentType']],
                                                     [file_entity.name, wikiattachments['fileName']])
    for results in copiedFileHandles['copyResults']:
        assert_is_none(results.get("failureCode"), "NOT FOUND and UNAUTHORIZED failure codes.")

    files = {file_entity.name: {"contentType": file_entity['contentType'], "md5": file_entity['md5']},
             wikiattachments['fileName']: {"contentType": wikiattachments['contentType'],
                                           "md5": wikiattachments['contentMd5']}}
    for results in copiedFileHandles['copyResults']:
        i = results['newFileHandle']
        assert_is_not_none(files.get(i['fileName']), "Filename has to be the same")
        assert_equals(files[i['fileName']]['contentType'], i['contentType'], "Content type has to be the same")
        assert_equals(files[i['fileName']]['md5'], i['contentMd5'], "Md5 has to be the same")

    for results in copiedFileHandles['copyResults']:
        assert_is_none(results.get("failureCode"), "There should not be NOT FOUND and UNAUTHORIZED failure codes.")

    # CHECK: Changing content type and downloadAs
    new_entity = synapseutils.changeFileMetaData(syn, file_entity, contentType="application/x-tar",
                                                 downloadAs="newName.txt")
    schedule_for_cleanup(new_entity.id)
    assert_equals(file_entity.md5, new_entity.md5, "Md5s must be equal after copying")
    fileResult = syn._getFileHandleDownload(new_entity.dataFileHandleId, new_entity.id)
    assert_equals(fileResult['fileHandle']['fileName'], "newName.txt", "Set new file name to be newName.txt")
    assert_equals(new_entity.contentType, "application/x-tar", "Set new content type to be application/x-tar")
Ejemplo n.º 11
0
def _copy_attachments(syn: Synapse, entity_wiki: Wiki) -> list:
    """Copy wiki attachments

    Args:
        syn: Synapse connection
        entity_wiki: Wiki you are copying

    Returns:
        Synapse Attachment filehandleids

    """
    # All attachments must be updated
    if entity_wiki['attachmentFileHandleIds']:
        attachments = [
            syn._getFileHandleDownload(filehandleid,
                                       entity_wiki.id,
                                       objectType='WikiAttachment')
            for filehandleid in entity_wiki['attachmentFileHandleIds']
        ]
        # Remove preview attachments
        no_previews = [
            attachment['fileHandle'] for attachment in attachments
            if attachment['fileHandle']['concreteType'] != PREVIEW_FILE_HANDLE
        ]
        content_types = [
            attachment['contentType'] for attachment in no_previews
        ]
        file_names = [attachment['fileName'] for attachment in no_previews]
        copied_filehandles = synapseutils.copyFileHandles(
            syn, no_previews, ["WikiAttachment"] * len(no_previews),
            [entity_wiki.id] * len(no_previews), content_types, file_names)
        new_attachments = [
            filehandle['newFileHandle']['id']
            for filehandle in copied_filehandles
        ]
    else:
        new_attachments = []
    return new_attachments
Ejemplo n.º 12
0
def test_copyFileHandleAndchangeFileMetadata():
    project_entity = syn.store(Project(name=str(uuid.uuid4())))
    schedule_for_cleanup(project_entity.id)
    filename = utils.make_bogus_data_file()
    attachname = utils.make_bogus_data_file()
    schedule_for_cleanup(filename)
    schedule_for_cleanup(attachname)
    file_entity = syn.store(File(filename, parent=project_entity))
    schedule_for_cleanup(file_entity.id)
    wiki = Wiki(owner=project_entity,
                title='A Test Wiki',
                markdown="testing",
                attachments=[attachname])
    wiki = syn.store(wiki)
    wikiattachments = syn._getFileHandle(wiki.attachmentFileHandleIds[0])
    # CHECK: Can batch copy two file handles (wiki attachments and file entity)
    copiedFileHandles = synapseutils.copyFileHandles(
        syn, [file_entity.dataFileHandleId, wiki.attachmentFileHandleIds[0]],
        [file_entity.concreteType.split(".")[-1], "WikiAttachment"],
        [file_entity.id, wiki.id],
        [file_entity.contentType, wikiattachments['contentType']],
        [file_entity.name, wikiattachments['fileName']])
    for results in copiedFileHandles['copyResults']:
        assert_is_none(results.get("failureCode"),
                       "NOT FOUND and UNAUTHORIZED failure codes.")

    files = {
        file_entity.name: {
            "contentType": file_entity['contentType'],
            "md5": file_entity['md5']
        },
        wikiattachments['fileName']: {
            "contentType": wikiattachments['contentType'],
            "md5": wikiattachments['contentMd5']
        }
    }
    for results in copiedFileHandles['copyResults']:
        i = results['newFileHandle']
        assert_is_not_none(files.get(i['fileName']),
                           "Filename has to be the same")
        assert_equals(files[i['fileName']]['contentType'], i['contentType'],
                      "Content type has to be the same")
        assert_equals(files[i['fileName']]['md5'], i['contentMd5'],
                      "Md5 has to be the same")

    for results in copiedFileHandles['copyResults']:
        assert_is_none(
            results.get("failureCode"),
            "There should not be NOT FOUND and UNAUTHORIZED failure codes.")

    # CHECK: Changing content type and downloadAs
    new_entity = synapseutils.changeFileMetaData(
        syn,
        file_entity,
        contentType="application/x-tar",
        downloadAs="newName.txt")
    schedule_for_cleanup(new_entity.id)
    assert_equals(file_entity.md5, new_entity.md5,
                  "Md5s must be equal after copying")
    fileResult = syn._getFileHandleDownload(new_entity.dataFileHandleId,
                                            new_entity.id)
    assert_equals(fileResult['fileHandle']['fileName'], "newName.txt",
                  "Set new file name to be newName.txt")
    assert_equals(new_entity.contentType, "application/x-tar",
                  "Set new content type to be application/x-tar")
Ejemplo n.º 13
0
def mirrorwiki(syn, entity, destination, force_merge=False):
    """
    This script is responsible for mirroring wiki pages
    It relies on the wiki titles between two Synapse Projects to be 
    The same and will merge the updates from entity's wikis to destination's wikis

    Args:
        entity: Synapse File, Project, Folder Entity or Id with Wiki you want to copy
        destination: Synapse File, Project, Folder Entity or Id with Wiki that matches entity
        force_merge: this will update a page even if its the same
    
    Returns:
        nothing
    """
    entity = syn.get(entity, downloadFile=False)
    destination = syn.get(destination, downloadFile=False)
    # TODO: getWikiHeaders fails when there is no wiki
    entity_wiki = syn.getWikiHeaders(entity)
    try:
        destination_wiki = syn.getWikiHeaders(destination)
    except synapseclient.exceptions.SynapseHTTPError as e:
        raise ValueError(
            "The destination project has no wiki page.  Do not confuse the mirror wiki script for the copy wiki script (synapseutils.copyWiki)"
        )

    #Get mapping of wiki pages
    entity_wiki_pages = {}
    for wiki in entity_wiki:
        entity_wiki_pages[wiki['title']] = wiki['id']
    destination_wiki_pages = {}
    #Mapping dictionary containing wiki page mapping between entity and destination
    wiki_mapping = {}
    for wiki in destination_wiki:
        destination_wiki_pages[wiki['title']] = wiki['id']
        #Account for pages that exist in the new page that don't exist in the old page
        if entity_wiki_pages.get(wiki['title']) is not None:
            wiki_mapping[entity_wiki_pages[wiki['title']]] = wiki['id']
    ### TODO: Need to account for new pages ###
    for title in entity_wiki_pages:
        entity_wiki = syn.getWiki(entity, entity_wiki_pages[title])
        #If destination wiki does not have the title page, do not update
        if destination_wiki_pages.get(title) is not None:
            destination_wiki = syn.getWiki(destination,
                                           destination_wiki_pages[title])
            if destination_wiki.markdown == entity_wiki.markdown and not force_merge:
                print("Skipping page update: %s" % title)
            else:
                print("Updating: %s" % title)
                destination_wiki.markdown = entity_wiki.markdown
                for entity_page_id in wiki_mapping:
                    entity_project_and_wiki_id = "%s/wiki/%s" % (
                        entity.id, entity_page_id)
                    destination_project_and_wiki_id = "%s/wiki/%s" % (
                        destination.id, wiki_mapping[entity_page_id])
                    destination_wiki.markdown = re.sub(
                        entity_project_and_wiki_id,
                        destination_project_and_wiki_id,
                        destination_wiki.markdown)
                destination_wiki.markdown = re.sub(entity.id, destination.id,
                                                   destination_wiki.markdown)
            #All attachments must be updated
            if entity_wiki['attachmentFileHandleIds'] == []:
                new_file_handles = []
            elif entity_wiki['attachmentFileHandleIds'] != []:
                attachments = [
                    syn._getFileHandleDownload(filehandleid,
                                               entity_wiki.id,
                                               objectType='WikiAttachment')
                    for filehandleid in entity_wiki['attachmentFileHandleIds']
                ]
                #Remove preview attachments
                no_previews = [
                    attachment['fileHandle'] for attachment in attachments
                    if attachment['fileHandle']['concreteType'] !=
                    "org.sagebionetworks.repo.model.file.PreviewFileHandle"
                ]
                content_types = [
                    attachment['contentType'] for attachment in no_previews
                ]
                file_names = [
                    attachment['fileName'] for attachment in no_previews
                ]
                copied_filehandles = synu.copyFileHandles(
                    syn, no_previews, ["WikiAttachment"] * len(no_previews),
                    [entity_wiki.id] * len(no_previews), content_types,
                    file_names)
                new_attachments = [
                    filehandle['newFileHandle']['id']
                    for filehandle in copied_filehandles['copyResults']
                ]
            destination_wiki.update(
                {'attachmentFileHandleIds': new_attachments})
            destination_wiki = syn.store(destination_wiki)
        else:
            print("%s: title not existent in destination wikis" % title)
Ejemplo n.º 14
0
def mirrorwiki(syn, entity, destination, force_merge=False):
    """
    This script is responsible for mirroring wiki pages
    It relies on the wiki titles between two Synapse Projects to be
    The same and will merge the updates from entity's wikis to
    destination's wikis

    Args:
        entity: Synapse File, Project, Folder Entity or Id with
                Wiki you want to copy
        destination: Synapse File, Project, Folder Entity or Id
                     with Wiki that matches entity
        force_merge: this will update a page even if its the same

    Returns:
        nothing
    """
    entity = syn.get(entity, downloadFile=False)
    destination = syn.get(destination, downloadFile=False)
    # TODO: getWikiHeaders fails when there is no wiki
    entity_wiki = syn.getWikiHeaders(entity)
    try:
        destination_wiki = syn.getWikiHeaders(destination)
    except SynapseHTTPError:
        raise ValueError("The destination Project has no Wiki. Mirroring a"
                         " Wiki requires that the source and destination "
                         "Projects have the same structure. You may want "
                         "to use the copy wiki functionality provided "
                         "by 'synapseutils.copyWiki'")

    # Get mapping of wiki pages
    entity_wiki_pages = {}
    for wiki in entity_wiki:
        entity_wiki_pages[wiki['title']] = wiki['id']
    destination_wiki_pages = {}
    # Mapping dictionary containing wiki page mapping between
    # entity and destination
    wiki_mapping = {}
    for wiki in destination_wiki:
        destination_wiki_pages[wiki['title']] = wiki['id']
        # Account for pages that exist in the new page that
        # don't exist in the old page
        if entity_wiki_pages.get(wiki['title']) is not None:
            wiki_mapping[entity_wiki_pages[wiki['title']]] = wiki['id']
    # TODO: Need to account for new pages ###
    for title in entity_wiki_pages:
        entity_wiki = syn.getWiki(entity, entity_wiki_pages[title])
        # If destination wiki does not have the title page, do not update
        if destination_wiki_pages.get(title) is not None:
            destination_wiki = syn.getWiki(destination,
                                           destination_wiki_pages[title])
            if destination_wiki.markdown == entity_wiki.markdown and not force_merge:
                logger.info("Skipping page update: {}".format(title))
            else:
                logger.info("Updating: {}".format(title))
                destination_wiki.markdown = entity_wiki.markdown
                for entity_page_id in wiki_mapping:
                    entity_project_and_wiki_id = "{}/wiki/{}".format(
                        entity.id,
                        entity_page_id)
                    destination_project_and_wiki_id = "{}/wiki/{}".format(
                        destination.id,
                        wiki_mapping[entity_page_id])
                    destination_wiki.markdown = re.sub(entity_project_and_wiki_id,
                                                       destination_project_and_wiki_id,
                                                       destination_wiki.markdown)
                    # Some widgets that you fill in with synapse links are auto encoded. / -> %2F
                    encoded_entity_project_and_wiki_id = "{}%2Fwiki%2F{}".format(entity.id, entity_page_id)
                    encoded_destination_project_and_wiki_id = "{}%2Fwiki%2F{}".format(destination.id, wiki_mapping[entity_page_id])
                    destination_wiki.markdown = re.sub(encoded_entity_project_and_wiki_id,
                                                       encoded_destination_project_and_wiki_id,
                                                       destination_wiki.markdown)
                destination_wiki.markdown = re.sub(entity.id,
                                                   destination.id,
                                                   destination_wiki.markdown)
            # All attachments must be updated
            if len(entity_wiki['attachmentFileHandleIds']) > 0:
                attachments = [syn._getFileHandleDownload(filehandleid, entity_wiki.id, objectType='WikiAttachment') for filehandleid in entity_wiki['attachmentFileHandleIds']]
                # Remove preview attachments
                no_previews = [attachment['fileHandle'] for attachment in attachments if attachment['fileHandle']['concreteType'] != "org.sagebionetworks.repo.model.file.PreviewFileHandle"]
                content_types = [attachment['contentType'] for attachment in no_previews]
                file_names = [attachment['fileName'] for attachment in no_previews]
                copied_filehandles = synapseutils.copyFileHandles(syn,
                                                                  no_previews,
                                                                  ["WikiAttachment"]*len(no_previews),
                                                                  [entity_wiki.id]*len(no_previews),
                                                                  content_types,
                                                                  file_names)
                new_attachments = [filehandle['newFileHandle']['id']
                                   for filehandle in copied_filehandles['copyResults']]
            else:
                new_attachments = []
            destination_wiki.update({'attachmentFileHandleIds': new_attachments})
            destination_wiki = syn.store(destination_wiki)
        else:
            logger.info("{}: title not existent in destination wikis".format(title))
 def copyFileHandles(self, fileHandles, associateObjectTypes, associateObjectIds, contentTypes, fileNames):
   return synapseutils.copyFileHandles(self.syn, fileHandles, associateObjectTypes, associateObjectIds, contentTypes, fileNames)