Skip to content

Entity

The Entity class is the base class for all entities, including Project, Folder, File, and Link.

Entities are dictionary-like objects in which both object and dictionary notation (entity.foo or entity['foo']) can be used interchangeably.

synapseclient.entity.Entity

Bases: MutableMapping

A Synapse entity is an object that has metadata, access control, and potentially a file. It can represent data, source code, or a folder that contains other entities.

Entities should typically be created using the constructors for specific subclasses such as synapseclient.Project, synapseclient.Folder or synapseclient.File.

ATTRIBUTE DESCRIPTION
id

The unique immutable ID for this entity. A new ID will be generated for new Entities. Once issued, this ID is guaranteed to never change or be re-issued

name

The name of this entity. Must be 256 characters or less. Names may only contain: letters, numbers, spaces, underscores, hyphens, periods, plus signs, apostrophes, and parentheses

description

The description of this entity. Must be 1000 characters or less.

parentId

The ID of the Entity that is the parent of this Entity.

entityType

The type of this entity.

concreteType

Indicates which implementation of Entity this object represents. The value is the fully qualified class name, e.g. org.sagebionetworks.repo.model.FileEntity.

etag

Synapse employs an Optimistic Concurrency Control (OCC) scheme to handle concurrent updates. Since the E-Tag changes every time an entity is updated it is used to detect when a client's current representation of an entity is out-of-date.

annotations

The dict of annotations for this entity.

accessControlList

The access control list for this entity.

createdOn

The date this entity was created.

createdBy

The ID of the user that created this entity.

modifiedOn

The date this entity was last modified.

modifiedBy

The ID of the user that last modified this entity.

Source code in synapseclient/entity.py
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
class Entity(collections.abc.MutableMapping):
    """
    A Synapse entity is an object that has metadata, access control, and potentially a file. It can represent data,
    source code, or a folder that contains other entities.

    Entities should typically be created using the constructors for specific subclasses
    such as [synapseclient.Project][], [synapseclient.Folder][] or [synapseclient.File][].

    Attributes:
        id: The unique immutable ID for this entity. A new ID will be generated for new
            Entities. Once issued, this ID is guaranteed to never change or be re-issued
        name: The name of this entity. Must be 256 characters or less. Names may only
                contain: letters, numbers, spaces, underscores, hyphens, periods, plus
                signs, apostrophes, and parentheses
        description: The description of this entity. Must be 1000 characters or less.
        parentId: The ID of the Entity that is the parent of this Entity.
        entityType: The type of this entity.
        concreteType: Indicates which implementation of Entity this object represents.
                        The value is the fully qualified class name, e.g.
                        org.sagebionetworks.repo.model.FileEntity.
        etag: Synapse employs an Optimistic Concurrency Control (OCC) scheme to handle
                concurrent updates. Since the E-Tag changes every time an entity is
                updated it is used to detect when a client's current representation of
                an entity is out-of-date.
        annotations: The dict of annotations for this entity.
        accessControlList: The access control list for this entity.
        createdOn: The date this entity was created.
        createdBy: The ID of the user that created this entity.
        modifiedOn: The date this entity was last modified.
        modifiedBy: The ID of the user that last modified this entity.
    """

    _synapse_entity_type = "org.sagebionetworks.repo.model.Entity"
    _property_keys = [
        "id",
        "name",
        "description",
        "parentId",
        "entityType",
        "concreteType",
        "uri",
        "etag",
        "annotations",
        "accessControlList",
        "createdOn",
        "createdBy",
        "modifiedOn",
        "modifiedBy",
    ]
    _local_keys = []

    @classmethod
    def create(cls, properties=None, annotations=None, local_state=None):
        """
        Create an Entity or a subclass given dictionaries of properties and annotations, as might be received from the
        Synapse Repository.

        Arguments:
            properties:  A map of Synapse properties

                - If 'concreteType' is defined in properties, we create the proper subclass of Entity. If not, give back the
                type whose constructor was called.
                - If passed an Entity as input, create a new Entity using the input entity as a prototype.
            annotations: A map of user defined annotations
            local_state: Internal use only
        """

        # Create a new Entity using an existing Entity as a prototype
        if isinstance(properties, Entity):
            if annotations is None:
                annotations = {}
            if local_state is None:
                local_state = {}
            annotations.update(properties.annotations)
            local_state.update(properties.local_state())
            properties = properties.properties
            if "id" in properties:
                del properties["id"]

        if (
            cls == Entity
            and "concreteType" in properties
            and properties["concreteType"] in entity_type_to_class
        ):
            cls = entity_type_to_class[properties["concreteType"]]
        return cls(
            properties=properties, annotations=annotations, local_state=local_state
        )

    @classmethod
    def getURI(cls, id):
        return "/entity/%s" % id

    def __new__(cls, *args, **kwargs):
        obj = object.__new__(cls)

        # Make really sure that properties and annotations exist before
        # any object methods get invoked. This is important because the
        # dot operator magic methods have been overridden and depend on
        # properties and annotations existing.
        obj.__dict__["properties"] = DictObject()
        obj.__dict__["annotations"] = DictObject()
        return obj

    def __init__(
        self, properties=None, annotations=None, local_state=None, parent=None, **kwargs
    ):
        if properties:
            if isinstance(properties, collections.abc.Mapping):
                if "annotations" in properties and isinstance(
                    properties["annotations"], collections.abc.Mapping
                ):
                    annotations.update(properties["annotations"])
                    del properties["annotations"]

                # Re-map `items` to `datasetItems` to avoid namespace conflicts
                # between Dataset schema and the items() builtin method.
                if "items" in properties:
                    properties["datasetItems"] = properties["items"]
                    del properties["items"]
                self.__dict__["properties"].update(properties)
            else:
                raise SynapseMalformedEntityError(
                    "Unknown argument type: properties is a %s" % str(type(properties))
                )

        if annotations:
            if isinstance(annotations, collections.abc.Mapping):
                self.__dict__["annotations"].update(annotations)
            elif isinstance(annotations, str):
                self.properties["annotations"] = annotations
            else:
                raise SynapseMalformedEntityError(
                    "Unknown argument type: annotations is a %s"
                    % str(type(annotations))
                )

        if local_state:
            if isinstance(local_state, collections.abc.Mapping):
                self.local_state(local_state)
            else:
                raise SynapseMalformedEntityError(
                    "Unknown argument type: local_state is a %s"
                    % str(type(local_state))
                )

        for key in self.__class__._local_keys:
            if key not in self.__dict__:
                self.__dict__[key] = None

        # Extract parentId from parent
        if "parentId" not in kwargs:
            if parent:
                try:
                    kwargs["parentId"] = id_of(parent)
                except Exception:
                    if isinstance(parent, Entity) and "id" not in parent:
                        raise SynapseMalformedEntityError(
                            "Couldn't find 'id' of parent."
                            " Has it been stored in Synapse?"
                        )
                    else:
                        raise SynapseMalformedEntityError(
                            "Couldn't find 'id' of parent."
                        )

        # Note: that this will work properly if derived classes declare their internal state variable *before* invoking
        # super(...).__init__(...)
        for key, value in kwargs.items():
            self.__setitem__(key, value)

        if "concreteType" not in self:
            self["concreteType"] = self.__class__._synapse_entity_type

        # Only project can be top-level. All other entity types require parentId don't enforce this for generic Entity
        if (
            "parentId" not in self
            and not isinstance(self, Project)
            and not type(self) == Entity
        ) and "id" not in self:
            raise SynapseMalformedEntityError(
                "Entities of type %s must have a parentId." % type(self)
            )

    def postURI(self):
        return "/entity"

    def putURI(self):
        return "/entity/%s" % self.id

    def deleteURI(self, versionNumber=None):
        if versionNumber:
            return "/entity/%s/version/%s" % (self.id, versionNumber)
        else:
            return "/entity/%s" % self.id

    def local_state(self, state: dict = None) -> dict:
        """
        Set or get the object's internal state, excluding properties, or annotations.

        Arguments:
            state: A dictionary containing the object's internal state.

        Returns:
            result: The object's internal state, excluding properties, or annotations.
        """
        if state:
            for key, value in state.items():
                if key not in ["annotations", "properties"]:
                    self.__dict__[key] = value
        result = {}
        for key, value in self.__dict__.items():
            if key not in ["annotations", "properties"] and not key.startswith("__"):
                result[key] = value
        return result

    def __setattr__(self, key, value):
        return self.__setitem__(key, value)

    def __setitem__(self, key, value):
        if key in self.__dict__ or key in self.__class__._local_keys:
            # If we assign like so:
            #   entity.annotations = {'foo';123, 'bar':'bat'}
            # Wrap the dictionary in a DictObject so we can
            # later do:
            #   entity.annotations.foo = 'bar'
            if (key == "annotations" or key == "properties") and not isinstance(
                value, DictObject
            ):
                value = DictObject(value)
            self.__dict__[key] = value
        elif key in self.__class__._property_keys:
            self.properties[key] = value
        else:
            self.annotations[key] = value

    # TODO: def __delattr__

    def __getattr__(self, key):
        # Note: that __getattr__ is only called after an attempt to
        # look the key up in the object's dictionary has failed.
        try:
            return self.__getitem__(key)
        except KeyError:
            # Note that hasattr in Python2 is more permissive than Python3
            # about what exceptions it catches. In Python3, hasattr catches
            # only AttributeError
            raise AttributeError(key)

    def __getitem__(self, key):
        if key in self.__dict__:
            return self.__dict__[key]
        elif key in self.properties:
            return self.properties[key]
        elif key in self.annotations:
            return self.annotations[key]
        else:
            raise KeyError(key)

    def __delitem__(self, key):
        if key in self.properties:
            del self.properties[key]
        elif key in self.annotations:
            del self.annotations[key]

    def __iter__(self):
        return iter(self.keys())

    def __len__(self):
        return len(self.keys())

    # TODO shouldn't these include local_state as well? -jcb
    def keys(self):
        """
        Returns:
            A set of property and annotation keys
        """
        return set(self.properties.keys()) | set(self.annotations.keys())

    def has_key(self, key):
        """Is the given key a property or annotation?"""

        return key in self.properties or key in self.annotations

    def _write_kvps(
        self,
        f: io.StringIO,
        dictionary: dict,
        key_filter: callable = None,
        key_aliases: dict = None,
    ) -> None:
        """
        Writes key-value pairs from a dictionary to a file.

        Arguments:
            f: The file object to write to.
            dictionary: The dictionary containing the key-value pairs.
            key_filter: A function that filters the keys.
                        Only keys that pass the filter will be written to the file.
                        Defaults to None.
            key_aliases: A dictionary mapping keys to their alias names.
                         If provided, the alias names will be used instead
                         of the original keys when writing to the file.
                         Defaults to None.
        """
        for key in sorted(dictionary.keys()):
            if (not key_filter) or key_filter(key):
                f.write("  ")
                f.write(str(key) if not key_aliases else key_aliases[key])
                f.write("=")
                f.write(str(dictionary[key]))
                f.write("\n")

    def __str__(self):
        """Returns a string representation of the object.

        Returns:
            str: A string representation of the object, including the class name,
                name property, id property (if available), properties, and annotations.
        """
        f = io.StringIO()

        f.write(
            "%s: %s (%s)\n"
            % (
                self.__class__.__name__,
                self.properties.get("name", "None"),
                self["id"] if "id" in self else "-",
            )
        )

        self._str_localstate(f)

        f.write("properties:\n")
        self._write_kvps(f, self.properties)

        f.write("annotations:\n")
        self._write_kvps(f, self.annotations)

        return f.getvalue()

    def _str_localstate(self, f: io.StringIO) -> None:
        """
        Helper method for writing the string representation
        of the local state to a StringIO object

        Arguments:
            f: a StringIO object to which the local state string will be written
        """
        self._write_kvps(
            f,
            self.__dict__,
            lambda key: not (
                key in ["properties", "annotations"] or key.startswith("__")
            ),
        )

    def __repr__(self):
        """Returns an eval-able representation of the Entity."""

        f = io.StringIO()
        f.write(self.__class__.__name__)
        f.write("(")
        f.write(
            ", ".join(
                {
                    "%s=%s"
                    % (
                        str(key),
                        value.__repr__(),
                    )
                    for key, value in itertools.chain(
                        list(
                            [
                                k_v
                                for k_v in self.__dict__.items()
                                if not (
                                    k_v[0] in ["properties", "annotations"]
                                    or k_v[0].startswith("__")
                                )
                            ]
                        ),
                        self.properties.items(),
                        self.annotations.items(),
                    )
                }
            )
        )
        f.write(")")
        return f.getvalue()

Functions

create(properties=None, annotations=None, local_state=None) classmethod

Create an Entity or a subclass given dictionaries of properties and annotations, as might be received from the Synapse Repository.

PARAMETER DESCRIPTION
properties

A map of Synapse properties

  • If 'concreteType' is defined in properties, we create the proper subclass of Entity. If not, give back the type whose constructor was called.
  • If passed an Entity as input, create a new Entity using the input entity as a prototype.

DEFAULT: None

annotations

A map of user defined annotations

DEFAULT: None

local_state

Internal use only

DEFAULT: None

Source code in synapseclient/entity.py
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
@classmethod
def create(cls, properties=None, annotations=None, local_state=None):
    """
    Create an Entity or a subclass given dictionaries of properties and annotations, as might be received from the
    Synapse Repository.

    Arguments:
        properties:  A map of Synapse properties

            - If 'concreteType' is defined in properties, we create the proper subclass of Entity. If not, give back the
            type whose constructor was called.
            - If passed an Entity as input, create a new Entity using the input entity as a prototype.
        annotations: A map of user defined annotations
        local_state: Internal use only
    """

    # Create a new Entity using an existing Entity as a prototype
    if isinstance(properties, Entity):
        if annotations is None:
            annotations = {}
        if local_state is None:
            local_state = {}
        annotations.update(properties.annotations)
        local_state.update(properties.local_state())
        properties = properties.properties
        if "id" in properties:
            del properties["id"]

    if (
        cls == Entity
        and "concreteType" in properties
        and properties["concreteType"] in entity_type_to_class
    ):
        cls = entity_type_to_class[properties["concreteType"]]
    return cls(
        properties=properties, annotations=annotations, local_state=local_state
    )

local_state(state=None)

Set or get the object's internal state, excluding properties, or annotations.

PARAMETER DESCRIPTION
state

A dictionary containing the object's internal state.

TYPE: dict DEFAULT: None

RETURNS DESCRIPTION
result

The object's internal state, excluding properties, or annotations.

TYPE: dict

Source code in synapseclient/entity.py
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
def local_state(self, state: dict = None) -> dict:
    """
    Set or get the object's internal state, excluding properties, or annotations.

    Arguments:
        state: A dictionary containing the object's internal state.

    Returns:
        result: The object's internal state, excluding properties, or annotations.
    """
    if state:
        for key, value in state.items():
            if key not in ["annotations", "properties"]:
                self.__dict__[key] = value
    result = {}
    for key, value in self.__dict__.items():
        if key not in ["annotations", "properties"] and not key.startswith("__"):
            result[key] = value
    return result

keys()

RETURNS DESCRIPTION

A set of property and annotation keys

Source code in synapseclient/entity.py
328
329
330
331
332
333
def keys(self):
    """
    Returns:
        A set of property and annotation keys
    """
    return set(self.properties.keys()) | set(self.annotations.keys())

has_key(key)

Is the given key a property or annotation?

Source code in synapseclient/entity.py
335
336
337
338
def has_key(self, key):
    """Is the given key a property or annotation?"""

    return key in self.properties or key in self.annotations

synapseclient.entity.Versionable

Bases: object

An entity for which Synapse will store a version history.

ATTRIBUTE DESCRIPTION
versionNumber

The version number issued to this version on the object.

versionLabel

The version label for this entity

versionComment

The version comment for this entity

versionUrl

The URL for this version

versions

A list of all versions

Source code in synapseclient/entity.py
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
class Versionable(object):
    """An entity for which Synapse will store a version history.

    Attributes:
        versionNumber: The version number issued to this version on the object.
        versionLabel:  	The version label for this entity
        versionComment: The version comment for this entity
        versionUrl:     The URL for this version
        versions:       A list of all versions

    """

    _synapse_entity_type = "org.sagebionetworks.repo.model.Versionable"
    _property_keys = [
        "versionNumber",
        "versionLabel",
        "versionComment",
        "versionUrl",
        "versions",
    ]