Skip to content

File

v5.0.0 uses the object-oriented models

This page documents the legacy File entity class. New code should use the object-oriented File model.

synapseclient.entity.File

Bases: Entity, Versionable

Represents a file in Synapse.

WARNING - This class is deprecated and will no longer be maintained. Please use the File model from synapseclient.models.File instead.

When a File object is stored, the associated local file or its URL will be stored in Synapse. A File must have a path (or URL) and a parent. By default, the name of the file in Synapse matches the filename, but by specifying the name attribute, the File Entity name can be different.

Changing File Names

A Synapse File Entity has a name separate from the name of the actual file it represents. When a file is uploaded to Synapse, its filename is fixed, even though the name of the entity can be changed at any time. Synapse provides a way to change this filename and the content-type of the file for future downloads by creating a new version of the file with a modified copy of itself. This can be done with the synapseutils.copy_functions.changeFileMetaData function.

import synapseutils
e = syn.get(synid)
print(os.path.basename(e.path))  ## prints, e.g., "my_file.txt"
e = synapseutils.changeFileMetaData(syn, e, "my_newname_file.txt")

Setting fileNameOverride will not change the name of a copy of the file that's already downloaded into your local cache. Either rename the local copy manually or remove it from the cache and re-download.:

syn.cache.remove(e.dataFileHandleId)
e = syn.get(e)
print(os.path.basename(e.path))  ## prints "my_newname_file.txt"
PARAMETER DESCRIPTION
path

Location to be represented by this File

DEFAULT: None

name

Name of the file in Synapse, not to be confused with the name within the path

parent

Project or Folder where this File is stored

DEFAULT: None

synapseStore

Whether the File should be uploaded or if only the path should be stored when synapseclient.Synapse.store is called on the File object.

DEFAULT: True

contentType

Manually specify Content-type header, for example "application/png" or "application/json; charset=UTF-8"

dataFileHandleId

Defining an existing dataFileHandleId will use the existing dataFileHandleId The creator of the file must also be the owner of the dataFileHandleId to have permission to store the file.

properties

A map of Synapse properties

DEFAULT: None

annotations

A map of user defined annotations

DEFAULT: None

local_state

Internal use only

DEFAULT: None

Creating instances

Creating and storing a File

# The Entity name is derived from the path and is 'data.xyz'
data = File('/path/to/file/data.xyz', parent=folder)
data = syn.store(data)

Setting the name of the file in Synapse to 'my entity'

# The Entity name is specified as 'my entity'
data = File('/path/to/file/data.xyz', name="my entity", parent=folder)
data = syn.store(data)
Migration to the object-oriented model

 

The legacy File class is created and persisted through the Synapse client. The object-oriented File model stores itself.

# Old approach (DEPRECATED)
# from synapseclient import File
# file = syn.store(File("path/to/file.txt", parent="syn123"))

# New approach (RECOMMENDED)
from synapseclient import Synapse
from synapseclient.models import File

syn = Synapse()
syn.login()

file = File(path="path/to/file.txt", parent_id="syn123").store()
print(file.id)
Source code in synapseclient/entity.py
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
@deprecated(
    version="4.14.0",
    reason="To be removed in 5.0.0. "
    "Use the File model from synapseclient.models.File instead.",
)
class File(Entity, Versionable):
    """
    Represents a file in Synapse.

    WARNING - This class is deprecated and will no longer be maintained. Please use the File model from synapseclient.models.File instead.

    When a File object is stored, the associated local file or its URL will be stored in Synapse.
    A File must have a path (or URL) and a parent. By default, the name of the file in Synapse
    matches the filename, but by specifying the `name` attribute, the File Entity name can be different.

    ## Changing File Names

    A Synapse File Entity has a name separate from the name of the actual file it represents.
    When a file is uploaded to Synapse, its filename is fixed, even though the name of the entity
    can be changed at any time. Synapse provides a way to change this filename and the
    content-type of the file for future downloads by creating a new version of the file
    with a modified copy of itself. This can be done with the
    [synapseutils.copy_functions.changeFileMetaData][] function.

        import synapseutils
        e = syn.get(synid)
        print(os.path.basename(e.path))  ## prints, e.g., "my_file.txt"
        e = synapseutils.changeFileMetaData(syn, e, "my_newname_file.txt")

    Setting *fileNameOverride* will **not** change the name of a copy of the
    file that's already downloaded into your local cache. Either rename the
    local copy manually or remove it from the cache and re-download.:

        syn.cache.remove(e.dataFileHandleId)
        e = syn.get(e)
        print(os.path.basename(e.path))  ## prints "my_newname_file.txt"

    Parameters:
        path: Location to be represented by this File
        name: Name of the file in Synapse, not to be confused with the name within the path
        parent: Project or Folder where this File is stored
        synapseStore: Whether the File should be uploaded or if only the path should
                        be stored when [synapseclient.Synapse.store][] is called on the File object.
        contentType: Manually specify Content-type header, for example "application/png" or
                        "application/json; charset=UTF-8"
        dataFileHandleId: Defining an existing dataFileHandleId will use the existing dataFileHandleId
                            The creator of the file must also be the owner of the dataFileHandleId
                            to have permission to store the file.
        properties: A map of Synapse properties
        annotations: A map of user defined annotations
        local_state: Internal use only

    Example: Creating instances
        Creating and storing a File

            # The Entity name is derived from the path and is 'data.xyz'
            data = File('/path/to/file/data.xyz', parent=folder)
            data = syn.store(data)

        Setting the name of the file in Synapse to 'my entity'

            # The Entity name is specified as 'my entity'
            data = File('/path/to/file/data.xyz', name="my entity", parent=folder)
            data = syn.store(data)

    Example: Migration to the object-oriented model
         

        The legacy File class is created and persisted through the Synapse
        client. The object-oriented File model stores itself.

        ```python
        # Old approach (DEPRECATED)
        # from synapseclient import File
        # file = syn.store(File("path/to/file.txt", parent="syn123"))

        # New approach (RECOMMENDED)
        from synapseclient import Synapse
        from synapseclient.models import File

        syn = Synapse()
        syn.login()

        file = File(path="path/to/file.txt", parent_id="syn123").store()
        print(file.id)
        ```
    """

    # Note: externalURL technically should not be in the keys since it's only a field/member variable of
    # ExternalFileHandle, but for backwards compatibility it's included
    _file_handle_keys = [
        "createdOn",
        "id",
        "concreteType",
        "contentSize",
        "createdBy",
        "etag",
        "fileName",
        "contentType",
        "contentMd5",
        "storageLocationId",
        "externalURL",
    ]
    # Used for backwards compatability. The keys found below used to located in the entity's local_state
    # (i.e. __dict__).
    _file_handle_aliases = {
        "md5": "contentMd5",
        "externalURL": "externalURL",
        "fileSize": "contentSize",
        "contentType": "contentType",
    }
    _file_handle_aliases_inverse = {v: k for k, v in _file_handle_aliases.items()}

    _property_keys = (
        Entity._property_keys + Versionable._property_keys + ["dataFileHandleId"]
    )
    _local_keys = Entity._local_keys + [
        "path",
        "cacheDir",
        "files",
        "synapseStore",
        "_file_handle",
    ]
    _synapse_entity_type = "org.sagebionetworks.repo.model.FileEntity"

    # TODO: File(path="/path/to/file", synapseStore=True, parentId="syn101")
    def __init__(
        self,
        path=None,
        parent=None,
        synapseStore=True,
        properties=None,
        annotations=None,
        local_state=None,
        **kwargs,
    ):
        if path and "name" not in kwargs:
            kwargs["name"] = utils.guess_file_name(path)
        self.__dict__["path"] = path
        if path:
            cacheDir, basename = os.path.split(path)
            self.__dict__["cacheDir"] = cacheDir
            self.__dict__["files"] = [basename]
        else:
            self.__dict__["cacheDir"] = None
            self.__dict__["files"] = []
        self.__dict__["synapseStore"] = synapseStore

        # pop the _file_handle from local properties because it is handled differently from other local_state
        self._update_file_handle(
            local_state.pop("_file_handle", None) if (local_state is not None) else None
        )

        super(File, self).__init__(
            concreteType=File._synapse_entity_type,
            properties=properties,
            annotations=annotations,
            local_state=local_state,
            parent=parent,
            **kwargs,
        )

    def _update_file_handle(self, file_handle_update_dict=None):
        """Sets the file handle. Should not need to be called by users.

        Args:
            file_handle_update_dict: A dictionary containing the file handle information.
        """

        # replace the file handle dict
        fh_dict = (
            DictObject(file_handle_update_dict)
            if file_handle_update_dict is not None
            else DictObject()
        )
        self.__dict__["_file_handle"] = fh_dict

        if (
            file_handle_update_dict is not None
            and file_handle_update_dict.get("concreteType")
            == "org.sagebionetworks.repo.model.file.ExternalFileHandle"
            and urllib_parse.urlparse(file_handle_update_dict.get("externalURL")).scheme
            != "sftp"
        ):
            self.__dict__["synapseStore"] = False

        # initialize all nonexistent keys to have value of None
        for key in self.__class__._file_handle_keys:
            if key not in fh_dict:
                fh_dict[key] = None

    def __setitem__(self, key, value):
        if key == "_file_handle":
            self._update_file_handle(value)
        elif key in self.__class__._file_handle_aliases:
            self._file_handle[self.__class__._file_handle_aliases[key]] = value
        else:

            def expand_and_convert_to_URL(path):
                return utils.as_url(os.path.expandvars(os.path.expanduser(path)))

            # hacky solution to allowing immediate switching into a ExternalFileHandle pointing to the current path
            # yes, there is boolean zen but I feel like it is easier to read/understand this way
            if (
                key == "synapseStore"
                and value is False
                and self["synapseStore"] is True
                and utils.caller_module_name(inspect.currentframe()) != "client"
            ):
                self["externalURL"] = expand_and_convert_to_URL(self["path"])

            # hacky solution because we historically allowed modifying 'path' to indicate wanting to change to a new
            # ExternalFileHandle
            # don't change exernalURL if it's just the synapseclient setting metadata after a function call such as
            # syn.get()
            if (
                key == "path"
                and not self["synapseStore"]
                and utils.caller_module_name(inspect.currentframe()) != "client"
                and utils.caller_module_name(inspect.currentframe())
                != "download_functions"
            ):
                self["externalURL"] = expand_and_convert_to_URL(value)
                self["contentMd5"] = None
                self["contentSize"] = None
            super(File, self).__setitem__(key, value)

    def __getitem__(self, item):
        if item in self.__class__._file_handle_aliases:
            return self._file_handle[self.__class__._file_handle_aliases[item]]
        else:
            return super(File, self).__getitem__(item)

    def _str_localstate(self, f):
        self._write_kvps(
            f,
            self._file_handle,
            lambda key: key
            in ["externalURL", "contentMd5", "contentSize", "contentType"],
            self._file_handle_aliases_inverse,
        )
        self._write_kvps(
            f,
            self.__dict__,
            lambda key: not (
                key in ["properties", "annotations", "_file_handle"]
                or key.startswith("__")
            ),
        )