From ceb55e4e817d5f2da6de5f104907a3906250457c Mon Sep 17 00:00:00 2001 From: erhe Date: Thu, 14 Mar 2024 20:35:12 +0100 Subject: [PATCH 1/6] lazy attribute population. --- source/ftrack_api/attribute.py | 93 +++++++++++++------------- source/ftrack_api/attribute_storage.py | 53 +++++++++++++++ source/ftrack_api/entity/base.py | 42 ++++++------ source/ftrack_api/inspection.py | 18 +++-- source/ftrack_api/query.py | 4 +- source/ftrack_api/session.py | 25 +++---- 6 files changed, 150 insertions(+), 85 deletions(-) create mode 100644 source/ftrack_api/attribute_storage.py diff --git a/source/ftrack_api/attribute.py b/source/ftrack_api/attribute.py index 4a8fdbab..0ba12a06 100644 --- a/source/ftrack_api/attribute.py +++ b/source/ftrack_api/attribute.py @@ -7,7 +7,9 @@ import collections from six.moves import collections_abc import copy +import typing import logging +import warnings import functools import ftrack_api.symbol @@ -16,6 +18,8 @@ import ftrack_api.inspection import ftrack_api.operation +from ftrack_api.attribute_storage import get_entity_storage + logger = logging.getLogger(__name__) @@ -25,6 +29,11 @@ def merge_references(function): @functools.wraps(function) def get_value(attribute, entity): """Merge the attribute with the local cache.""" + mergable_types = ( + ftrack_api.entity.base.Entity, + ftrack_api.collection.Collection, + ftrack_api.collection.MappedCollectionProxy, + ) if attribute.name not in entity._inflated: # Only merge on first access to avoid @@ -39,11 +48,7 @@ def get_value(attribute, entity): local_value = attribute.get_local_value(entity) if isinstance( local_value, - ( - ftrack_api.entity.base.Entity, - ftrack_api.collection.Collection, - ftrack_api.collection.MappedCollectionProxy, - ), + mergable_types, ): logger.debug("Merging local value for attribute {0}.".format(attribute)) @@ -57,11 +62,7 @@ def get_value(attribute, entity): remote_value = attribute.get_remote_value(entity) if isinstance( remote_value, - ( - ftrack_api.entity.base.Entity, - ftrack_api.collection.Collection, - ftrack_api.collection.MappedCollectionProxy, - ), + mergable_types, ): logger.debug( "Merging remote value for attribute {0}.".format(attribute) @@ -164,29 +165,23 @@ def __init__( self._computed = computed self.default_value = default_value - self._local_key = "local" - self._remote_key = "remote" - def __repr__(self): """Return representation of entity.""" return "<{0}.{1}({2}) object at {3}>".format( self.__module__, self.__class__.__name__, self.name, id(self) ) - def get_entity_storage(self, entity): + @staticmethod + def get_entity_storage(entity): """Return attribute storage on *entity* creating if missing.""" - storage_key = "_ftrack_attribute_storage" - storage = getattr(entity, storage_key, None) - if storage is None: - storage = collections.defaultdict( - lambda: { - self._local_key: ftrack_api.symbol.NOT_SET, - self._remote_key: ftrack_api.symbol.NOT_SET, - } - ) - setattr(entity, storage_key, storage) - return storage + warnings.warn( + "Use of Attribute.get_entity_storage is deprecated, use ftrack_api.attribute_storage" + ".get_entity_storage function instead.", + DeprecationWarning, + ) + + return get_entity_storage(entity) @property def name(self): @@ -211,24 +206,26 @@ def get_value(self, entity): via the session and block until available. """ - value = self.get_local_value(entity) - if value is not ftrack_api.symbol.NOT_SET: - return value + local_value, remote_remote = get_entity_storage(entity).get_local_remote_pair( + self.name + ) - value = self.get_remote_value(entity) - if value is not ftrack_api.symbol.NOT_SET: - return value + if local_value is not ftrack_api.symbol.NOT_SET: + return local_value + + if remote_remote is not ftrack_api.symbol.NOT_SET: + return remote_remote if not entity.session.auto_populate: - return value + return remote_remote self.populate_remote_value(entity) + return self.get_remote_value(entity) def get_local_value(self, entity): """Return locally set value for *entity*.""" - storage = self.get_entity_storage(entity) - return storage[self.name][self._local_key] + return get_entity_storage(entity).get_local(self.name) def get_remote_value(self, entity): """Return remote value for *entity*. @@ -238,8 +235,7 @@ def get_remote_value(self, entity): Only return locally stored remote value, do not fetch from remote. """ - storage = self.get_entity_storage(entity) - return storage[self.name][self._remote_key] + return get_entity_storage(entity).get_remote(self.name) def set_local_value(self, entity, value): """Set local *value* for *entity*.""" @@ -252,8 +248,7 @@ def set_local_value(self, entity, value): old_value = self.get_local_value(entity) - storage = self.get_entity_storage(entity) - storage[self.name][self._local_key] = value + get_entity_storage(entity).set_local(self.name, value) # Record operation. if entity.session.record_operations: @@ -275,8 +270,7 @@ def set_remote_value(self, entity, value): Only set locally stored remote value, do not persist to remote. """ - storage = self.get_entity_storage(entity) - storage[self.name][self._remote_key] = value + get_entity_storage(entity).set_remote(self.name, value) def populate_remote_value(self, entity): """Populate remote value for *entity*.""" @@ -291,18 +285,24 @@ def is_modified(self, entity): are the same on the remote. """ - local_value = self.get_local_value(entity) - remote_value = self.get_remote_value(entity) + local_value, remote_value = get_entity_storage(entity).get_local_remote_pair( + self.name + ) + return ( local_value is not ftrack_api.symbol.NOT_SET and local_value != remote_value ) def is_set(self, entity): """Return whether a value is set for *entity*.""" + local_value, remote_value = get_entity_storage(entity).get_local_remote_pair( + self.name + ) + return any( [ - self.get_local_value(entity) is not ftrack_api.symbol.NOT_SET, - self.get_remote_value(entity) is not ftrack_api.symbol.NOT_SET, + local_value is not ftrack_api.symbol.NOT_SET, + remote_value is not ftrack_api.symbol.NOT_SET, ] ) @@ -352,8 +352,9 @@ def is_modified(self, entity): are the same on the remote. """ - local_value = self.get_local_value(entity) - remote_value = self.get_remote_value(entity) + local_value, remote_value = get_entity_storage(entity).get_local_remote_pair( + self.name + ) if local_value is ftrack_api.symbol.NOT_SET: return False diff --git a/source/ftrack_api/attribute_storage.py b/source/ftrack_api/attribute_storage.py new file mode 100644 index 00000000..d3beee63 --- /dev/null +++ b/source/ftrack_api/attribute_storage.py @@ -0,0 +1,53 @@ +import typing +import collections + +from ftrack_api.symbol import NOT_SET + +if typing.TYPE_CHECKING: + from ftrack_api.entity.base import Entity + +ENTITY_STORAGE_KEY = "ftrack_attribute_storage" + +LOCAL_ENTITY_STORAGE_KEY = "local" +REMOTE_ENTITY_STORAGE_KEY = "remote" + + +class EntityStorage(collections.defaultdict): + """Storage for entity attributes""" + + def get(self, key: str) -> typing.Any: + local, remote = self.get_local_remote_pair(key) + + return local if local is not NOT_SET else remote + + def get_local(self, key: str) -> typing.Any: + return self[key][LOCAL_ENTITY_STORAGE_KEY] + + def get_remote(self, key: str) -> typing.Any: + return self[key][REMOTE_ENTITY_STORAGE_KEY] + + def get_local_remote_pair(self, key: str) -> typing.Tuple[typing.Any, typing.Any]: + """Return local and remote values for *key*.""" + return self[key][LOCAL_ENTITY_STORAGE_KEY], self[key][REMOTE_ENTITY_STORAGE_KEY] + + def set_local(self, key: str, value: typing.Any) -> None: + self[key][LOCAL_ENTITY_STORAGE_KEY] = value + + def set_remote(self, key: str, value: typing.Any) -> None: + self[key][REMOTE_ENTITY_STORAGE_KEY] = value + + +def get_entity_storage(entity: "Entity") -> EntityStorage: + """Return attribute storage on *entity* creating if missing.""" + + storage = getattr(entity, ENTITY_STORAGE_KEY, None) + if storage is None: + storage = EntityStorage( + lambda: { + LOCAL_ENTITY_STORAGE_KEY: NOT_SET, + REMOTE_ENTITY_STORAGE_KEY: NOT_SET, + } + ) + setattr(entity, ENTITY_STORAGE_KEY, storage) + + return storage diff --git a/source/ftrack_api/entity/base.py b/source/ftrack_api/entity/base.py index cf8fa4b7..3814f078 100644 --- a/source/ftrack_api/entity/base.py +++ b/source/ftrack_api/entity/base.py @@ -16,6 +16,8 @@ import ftrack_api.operation from ftrack_api.logging import LazyLogMessage as L from future.utils import with_metaclass +from ftrack_api.attribute import get_entity_storage +from ftrack_api.symbol import NOT_SET class _EntityBase(object): @@ -147,7 +149,7 @@ def _construct(self, data): # collections that are automatically generated on access. for attribute in self.attributes: value = attribute.get_local_value(self) - if value is not ftrack_api.symbol.NOT_SET: + if value is not NOT_SET: entity_data[attribute.name] = value self.session.recorded_operations.push( @@ -250,7 +252,7 @@ def __delitem__(self, key): """ attribute = self.__class__.attributes.get(key) - attribute.set_local_value(self, ftrack_api.symbol.NOT_SET) + attribute.set_local_value(self, NOT_SET) def __iter__(self): """Iterate over all attributes keys.""" @@ -310,6 +312,9 @@ def merge(self, entity, merged=None): log_message = 'Merged {type} "{name}": {old_value!r} -> {new_value!r}' changes = [] + storage = get_entity_storage(self) + other_storage = get_entity_storage(entity) + # Attributes. # Prioritise by type so that scalar values are set first. This should @@ -317,19 +322,19 @@ def merge(self, entity, merged=None): # are merged before merging any collections that may have references to # this entity. attributes = collections.deque() - for attribute in entity.attributes: + for attribute_name in other_storage.keys(): + attribute = self.attributes.get(attribute_name) if isinstance(attribute, ftrack_api.attribute.ScalarAttribute): - attributes.appendleft(attribute) + attributes.appendleft(attribute_name) else: - attributes.append(attribute) + attributes.append(attribute_name) - for other_attribute in attributes: - attribute = self.attributes.get(other_attribute.name) + for attribute_name in attributes: # Local attributes. - other_local_value = other_attribute.get_local_value(entity) - if other_local_value is not ftrack_api.symbol.NOT_SET: - local_value = attribute.get_local_value(self) + other_local_value = other_storage.get_local(attribute_name) + if other_local_value is not NOT_SET: + local_value = storage.get_local(attribute_name) if local_value != other_local_value: merged_local_value = self.session.merge( other_local_value, merged=merged @@ -347,9 +352,11 @@ def merge(self, entity, merged=None): log_debug and self.logger.debug(log_message.format(**changes[-1])) # Remote attributes. - other_remote_value = other_attribute.get_remote_value(entity) - if other_remote_value is not ftrack_api.symbol.NOT_SET: - remote_value = attribute.get_remote_value(self) + other_remote_value = other_storage.get_remote(attribute_name) + + if other_remote_value is not NOT_SET: + remote_value = storage.get_remote(attribute_name) + if remote_value != other_remote_value: merged_remote_value = self.session.merge( other_remote_value, merged=merged @@ -376,13 +383,10 @@ def merge(self, entity, merged=None): ): continue - local_value = attribute.get_local_value(self) + local_value = storage.get_local(attribute_name) # Populated but not modified, update it. - if ( - local_value is not ftrack_api.symbol.NOT_SET - and local_value == remote_value - ): + if local_value is not NOT_SET and local_value == remote_value: attribute.set_local_value(self, merged_remote_value) changes.append( { @@ -404,7 +408,7 @@ def _populate_unset_scalar_attributes(self): projections = [] for attribute in self.attributes: if isinstance(attribute, ftrack_api.attribute.ScalarAttribute): - if attribute.get_remote_value(self) is ftrack_api.symbol.NOT_SET: + if attribute.get_remote_value(self) is NOT_SET: projections.append(attribute.name) if projections: diff --git a/source/ftrack_api/inspection.py b/source/ftrack_api/inspection.py index a9cd8d1e..73f038c4 100644 --- a/source/ftrack_api/inspection.py +++ b/source/ftrack_api/inspection.py @@ -8,6 +8,9 @@ import ftrack_api.symbol import ftrack_api.operation +from ftrack_api.symbol import NOT_SET +from ftrack_api.attribute_storage import get_entity_storage + def identity(entity): """Return unique identity of *entity*.""" @@ -23,9 +26,12 @@ def primary_key(entity): """ primary_key = collections.OrderedDict() + entity_storage = get_entity_storage(entity) + for name in entity.primary_key_attributes: - value = entity[name] - if value is ftrack_api.symbol.NOT_SET: + value = entity_storage.get(name) + + if value is NOT_SET: raise KeyError( 'Missing required value for primary key attribute "{0}" on ' "entity {1!r}.".format(name, entity) @@ -41,13 +47,13 @@ def _state(operation, state): """Return state following *operation* against current *state*.""" if ( isinstance(operation, ftrack_api.operation.CreateEntityOperation) - and state is ftrack_api.symbol.NOT_SET + and state is NOT_SET ): state = ftrack_api.symbol.CREATED elif ( isinstance(operation, ftrack_api.operation.UpdateEntityOperation) - and state is ftrack_api.symbol.NOT_SET + and state is NOT_SET ): state = ftrack_api.symbol.MODIFIED @@ -63,7 +69,7 @@ def state(entity): .. seealso:: :func:`ftrack_api.inspection.states`. """ - value = ftrack_api.symbol.NOT_SET + value = NOT_SET for operation in entity.session.recorded_operations: # Determine if operation refers to an entity and whether that entity @@ -106,7 +112,7 @@ def states(entities): entities_by_identity = collections.OrderedDict() for entity in entities: key = (entity.entity_type, str(list(primary_key(entity).values()))) - entities_by_identity[key] = ftrack_api.symbol.NOT_SET + entities_by_identity[key] = NOT_SET for operation in session.recorded_operations: if isinstance( diff --git a/source/ftrack_api/query.py b/source/ftrack_api/query.py index 62c7dd5b..5b4a3136 100644 --- a/source/ftrack_api/query.py +++ b/source/ftrack_api/query.py @@ -10,8 +10,8 @@ class QueryResult(collections_abc.Sequence): """Results from a query.""" - OFFSET_EXPRESSION = re.compile("(?Poffset (?P\d+))") - LIMIT_EXPRESSION = re.compile("(?Plimit (?P\d+))") + OFFSET_EXPRESSION = re.compile(r"(?Poffset (?P\d+))") + LIMIT_EXPRESSION = re.compile(r"(?Plimit (?P\d+))") def __init__(self, session, expression, page_size=500): """Initialise result set. diff --git a/source/ftrack_api/session.py b/source/ftrack_api/session.py index d8a4f69e..4ae0a999 100644 --- a/source/ftrack_api/session.py +++ b/source/ftrack_api/session.py @@ -54,6 +54,7 @@ import ftrack_api._centralized_storage_scenario import ftrack_api.logging from ftrack_api.logging import LazyLogMessage as L +from ftrack_api.attribute import get_entity_storage try: from weakref import WeakMethod @@ -946,22 +947,22 @@ def _merge_recursive(self, entity, merged=None): if merged is None: merged = {} + mergable_types = ( + ftrack_api.entity.base.Entity, + ftrack_api.collection.Collection, + ftrack_api.collection.MappedCollectionProxy, + ) + attached = self.merge(entity, merged) + entity_storage = get_entity_storage(entity) - for attribute in entity.attributes: + for attribute_name in entity_storage.keys(): # Remote attributes. - remote_value = attribute.get_remote_value(entity) - - if isinstance( - remote_value, - ( - ftrack_api.entity.base.Entity, - ftrack_api.collection.Collection, - ftrack_api.collection.MappedCollectionProxy, - ), - ): + remote_value = entity_storage.get_remote(attribute_name) + + if isinstance(remote_value, mergable_types): log_debug and self.logger.debug( - "Merging remote value for attribute {0}.".format(attribute) + "Merging remote value for attribute {0}.".format(attribute_name) ) if isinstance(remote_value, ftrack_api.entity.base.Entity): From fb05c54594a0652b32f3045d147ce15537b2d863 Mon Sep 17 00:00:00 2001 From: erhe Date: Thu, 14 Mar 2024 21:07:42 +0100 Subject: [PATCH 2/6] setting incorrect attributes. --- source/ftrack_api/entity/base.py | 37 +++++++++++++++++--------------- 1 file changed, 20 insertions(+), 17 deletions(-) diff --git a/source/ftrack_api/entity/base.py b/source/ftrack_api/entity/base.py index 3814f078..441e247a 100644 --- a/source/ftrack_api/entity/base.py +++ b/source/ftrack_api/entity/base.py @@ -322,15 +322,13 @@ def merge(self, entity, merged=None): # are merged before merging any collections that may have references to # this entity. attributes = collections.deque() - for attribute_name in other_storage.keys(): - attribute = self.attributes.get(attribute_name) - if isinstance(attribute, ftrack_api.attribute.ScalarAttribute): - attributes.appendleft(attribute_name) + for _attribute in other_storage.keys(): + if isinstance(_attribute, ftrack_api.attribute.ScalarAttribute): + attributes.appendleft(_attribute) else: - attributes.append(attribute_name) + attributes.append(_attribute) for attribute_name in attributes: - # Local attributes. other_local_value = other_storage.get_local(attribute_name) if other_local_value is not NOT_SET: @@ -340,11 +338,12 @@ def merge(self, entity, merged=None): other_local_value, merged=merged ) - attribute.set_local_value(self, merged_local_value) + storage.set_local(attribute_name, merged_local_value) + changes.append( { "type": "local_attribute", - "name": attribute.name, + "name": attribute_name, "old_value": local_value, "new_value": merged_local_value, } @@ -353,21 +352,19 @@ def merge(self, entity, merged=None): # Remote attributes. other_remote_value = other_storage.get_remote(attribute_name) - if other_remote_value is not NOT_SET: remote_value = storage.get_remote(attribute_name) - if remote_value != other_remote_value: merged_remote_value = self.session.merge( other_remote_value, merged=merged ) - attribute.set_remote_value(self, merged_remote_value) + storage.set_remote(attribute_name, merged_remote_value) changes.append( { "type": "remote_attribute", - "name": attribute.name, + "name": attribute_name, "old_value": remote_value, "new_value": merged_remote_value, } @@ -379,19 +376,25 @@ def merge(self, entity, merged=None): # they may store a local copy of the remote attribute # even though it may not be modified. if not isinstance( - attribute, ftrack_api.attribute.AbstractCollectionAttribute + self.attributes.get(attribute_name), + ftrack_api.attribute.AbstractCollectionAttribute, ): continue - local_value = storage.get_local(attribute_name) + local_value = storage.get_local( + attribute_name + ) # attribute.get_local_value(self) # Populated but not modified, update it. - if local_value is not NOT_SET and local_value == remote_value: - attribute.set_local_value(self, merged_remote_value) + if ( + local_value is not ftrack_api.symbol.NOT_SET + and local_value == remote_value + ): + storage.set_local(attribute_name, merged_remote_value) changes.append( { "type": "local_attribute", - "name": attribute.name, + "name": attribute_name, "old_value": local_value, "new_value": merged_remote_value, } From 67e59908d98072ad7f2b4c41f8f30338e6dd7b8c Mon Sep 17 00:00:00 2001 From: erhe Date: Thu, 14 Mar 2024 21:37:54 +0100 Subject: [PATCH 3/6] use attribute setter --- source/ftrack_api/entity/base.py | 21 ++++++++++----------- 1 file changed, 10 insertions(+), 11 deletions(-) diff --git a/source/ftrack_api/entity/base.py b/source/ftrack_api/entity/base.py index 441e247a..28a38cac 100644 --- a/source/ftrack_api/entity/base.py +++ b/source/ftrack_api/entity/base.py @@ -149,7 +149,7 @@ def _construct(self, data): # collections that are automatically generated on access. for attribute in self.attributes: value = attribute.get_local_value(self) - if value is not NOT_SET: + if value is not ftrack_api.symbol.NOT_SET: entity_data[attribute.name] = value self.session.recorded_operations.push( @@ -252,7 +252,7 @@ def __delitem__(self, key): """ attribute = self.__class__.attributes.get(key) - attribute.set_local_value(self, NOT_SET) + attribute.set_local_value(self, ftrack_api.symbol.NOT_SET) def __iter__(self): """Iterate over all attributes keys.""" @@ -329,6 +329,8 @@ def merge(self, entity, merged=None): attributes.append(_attribute) for attribute_name in attributes: + attribute = self.attributes.get(attribute_name) + # Local attributes. other_local_value = other_storage.get_local(attribute_name) if other_local_value is not NOT_SET: @@ -338,7 +340,7 @@ def merge(self, entity, merged=None): other_local_value, merged=merged ) - storage.set_local(attribute_name, merged_local_value) + attribute.set_local_value(self, merged_local_value) changes.append( { @@ -359,7 +361,7 @@ def merge(self, entity, merged=None): other_remote_value, merged=merged ) - storage.set_remote(attribute_name, merged_remote_value) + attribute.set_remote_value(self, merged_remote_value) changes.append( { @@ -376,25 +378,22 @@ def merge(self, entity, merged=None): # they may store a local copy of the remote attribute # even though it may not be modified. if not isinstance( - self.attributes.get(attribute_name), - ftrack_api.attribute.AbstractCollectionAttribute, + attribute, ftrack_api.attribute.AbstractCollectionAttribute ): continue - local_value = storage.get_local( - attribute_name - ) # attribute.get_local_value(self) + local_value = attribute.get_local_value(self) # Populated but not modified, update it. if ( local_value is not ftrack_api.symbol.NOT_SET and local_value == remote_value ): - storage.set_local(attribute_name, merged_remote_value) + attribute.set_local_value(self, merged_remote_value) changes.append( { "type": "local_attribute", - "name": attribute_name, + "name": attribute.name, "old_value": local_value, "new_value": merged_remote_value, } From 574dac4ab7a4301971c288c6f0626fb3495bed94 Mon Sep 17 00:00:00 2001 From: erhe Date: Thu, 14 Mar 2024 21:51:26 +0100 Subject: [PATCH 4/6] import --- source/ftrack_api/attribute.py | 36 +++++++++++++--------------------- source/ftrack_api/session.py | 9 +++++---- 2 files changed, 19 insertions(+), 26 deletions(-) diff --git a/source/ftrack_api/attribute.py b/source/ftrack_api/attribute.py index 0ba12a06..a83d0658 100644 --- a/source/ftrack_api/attribute.py +++ b/source/ftrack_api/attribute.py @@ -12,12 +12,12 @@ import warnings import functools -import ftrack_api.symbol import ftrack_api.exception import ftrack_api.collection import ftrack_api.inspection import ftrack_api.operation +from ftrack_api.symbol import NOT_SET from ftrack_api.attribute_storage import get_entity_storage logger = logging.getLogger(__name__) @@ -140,7 +140,7 @@ class Attribute(object): def __init__( self, name, - default_value=ftrack_api.symbol.NOT_SET, + default_value=NOT_SET, mutable=True, computed=False, ): @@ -210,10 +210,10 @@ def get_value(self, entity): self.name ) - if local_value is not ftrack_api.symbol.NOT_SET: + if local_value is not NOT_SET: return local_value - if remote_remote is not ftrack_api.symbol.NOT_SET: + if remote_remote is not NOT_SET: return remote_remote if not entity.session.auto_populate: @@ -239,11 +239,7 @@ def get_remote_value(self, entity): def set_local_value(self, entity, value): """Set local *value* for *entity*.""" - if ( - not self.mutable - and self.is_set(entity) - and value is not ftrack_api.symbol.NOT_SET - ): + if not self.mutable and self.is_set(entity) and value is not NOT_SET: raise ftrack_api.exception.ImmutableAttributeError(self) old_value = self.get_local_value(entity) @@ -289,9 +285,7 @@ def is_modified(self, entity): self.name ) - return ( - local_value is not ftrack_api.symbol.NOT_SET and local_value != remote_value - ) + return local_value is not NOT_SET and local_value != remote_value def is_set(self, entity): """Return whether a value is set for *entity*.""" @@ -301,8 +295,8 @@ def is_set(self, entity): return any( [ - local_value is not ftrack_api.symbol.NOT_SET, - remote_value is not ftrack_api.symbol.NOT_SET, + local_value is not NOT_SET, + remote_value is not NOT_SET, ] ) @@ -356,10 +350,10 @@ def is_modified(self, entity): self.name ) - if local_value is ftrack_api.symbol.NOT_SET: + if local_value is NOT_SET: return False - if remote_value is ftrack_api.symbol.NOT_SET: + if remote_value is NOT_SET: return True if ftrack_api.inspection.identity( @@ -401,9 +395,7 @@ def get_value(self, entity): # mutated without side effects. local_value = self.get_local_value(entity) remote_value = self.get_remote_value(entity) - if local_value is ftrack_api.symbol.NOT_SET and isinstance( - remote_value, self.collection_class - ): + if local_value is NOT_SET and isinstance(remote_value, self.collection_class): try: with entity.session.operation_recording(False): self.set_local_value(entity, copy.copy(remote_value)) @@ -418,7 +410,7 @@ def get_value(self, entity): # newly created entity for example. It *could* be done as a simple # default value, but that would incur cost for every collection even # when they are not modified before commit. - if value is ftrack_api.symbol.NOT_SET: + if value is NOT_SET: try: with entity.session.operation_recording(False): self.set_local_value( @@ -433,7 +425,7 @@ def get_value(self, entity): def set_local_value(self, entity, value): """Set local *value* for *entity*.""" - if value is not ftrack_api.symbol.NOT_SET: + if value is not NOT_SET: value = self._adapt_to_collection(entity, value) value.mutable = self.mutable @@ -447,7 +439,7 @@ def set_remote_value(self, entity, value): Only set locally stored remote value, do not persist to remote. """ - if value is not ftrack_api.symbol.NOT_SET: + if value is not NOT_SET: value = self._adapt_to_collection(entity, value) value.mutable = False diff --git a/source/ftrack_api/session.py b/source/ftrack_api/session.py index 4ae0a999..ce256cb6 100644 --- a/source/ftrack_api/session.py +++ b/source/ftrack_api/session.py @@ -54,6 +54,7 @@ import ftrack_api._centralized_storage_scenario import ftrack_api.logging from ftrack_api.logging import LazyLogMessage as L +from ftrack_api.symbol import NOT_SET from ftrack_api.attribute import get_entity_storage try: @@ -1263,7 +1264,7 @@ def commit(self): for payload in batch: entity_data = payload.get("entity_data", {}) for key, value in list(entity_data.items()): - if value is ftrack_api.symbol.NOT_SET: + if value is NOT_SET: del entity_data[key] # Remove payloads with redundant entity_data. @@ -1736,7 +1737,7 @@ def _encode(self, item, entity_attribute_strategy="set_only"): with self.auto_populating(True): for attribute in item.attributes: - value = ftrack_api.symbol.NOT_SET + value = NOT_SET if entity_attribute_strategy == "all": value = attribute.get_value(item) @@ -1744,7 +1745,7 @@ def _encode(self, item, entity_attribute_strategy="set_only"): elif entity_attribute_strategy == "set_only": if attribute.is_set(item): value = attribute.get_local_value(item) - if value is ftrack_api.symbol.NOT_SET: + if value is NOT_SET: value = attribute.get_remote_value(item) elif entity_attribute_strategy == "modified_only": @@ -1755,7 +1756,7 @@ def _encode(self, item, entity_attribute_strategy="set_only"): if not attribute.computed: value = attribute.get_remote_value(item) - if value is not ftrack_api.symbol.NOT_SET: + if value is not NOT_SET: if isinstance( attribute, ftrack_api.attribute.ReferenceAttribute ): From 34acbf1f5fd9e0195be71f26c5e8da0ee7df5a03 Mon Sep 17 00:00:00 2001 From: erhe Date: Thu, 14 Mar 2024 22:11:22 +0100 Subject: [PATCH 5/6] more imports. --- source/ftrack_api/session.py | 32 +++++++++++++++++--------------- 1 file changed, 17 insertions(+), 15 deletions(-) diff --git a/source/ftrack_api/session.py b/source/ftrack_api/session.py index ce256cb6..2a37f906 100644 --- a/source/ftrack_api/session.py +++ b/source/ftrack_api/session.py @@ -57,12 +57,22 @@ from ftrack_api.symbol import NOT_SET from ftrack_api.attribute import get_entity_storage +from ftrack_api.entity.base import Entity +from ftrack_api.collection import Collection, MappedCollectionProxy + try: from weakref import WeakMethod except ImportError: from ftrack_api._weakref import WeakMethod +_MERGABLE_TYPES = ( + Entity, + Collection, + MappedCollectionProxy, +) + + class SessionAuthentication(requests.auth.AuthBase): """Attach ftrack session authentication information to requests.""" @@ -905,14 +915,14 @@ def _merge(self, value, merged): log_debug = self.logger.isEnabledFor(logging.DEBUG) with self.merge_lock: - if isinstance(value, ftrack_api.entity.base.Entity): + if isinstance(value, Entity): log_debug and self.logger.debug( "Merging entity into session: {0} at {1}".format(value, id(value)) ) return self._merge_entity(value, merged=merged) - elif isinstance(value, ftrack_api.collection.Collection): + elif isinstance(value, Collection): log_debug and self.logger.debug( "Merging collection into session: {0!r} at {1}".format( value, id(value) @@ -925,7 +935,7 @@ def _merge(self, value, merged): return merged_collection - elif isinstance(value, ftrack_api.collection.MappedCollectionProxy): + elif isinstance(value, MappedCollectionProxy): log_debug and self.logger.debug( "Merging mapped collection into session: {0!r} at {1}".format( value, id(value) @@ -948,12 +958,6 @@ def _merge_recursive(self, entity, merged=None): if merged is None: merged = {} - mergable_types = ( - ftrack_api.entity.base.Entity, - ftrack_api.collection.Collection, - ftrack_api.collection.MappedCollectionProxy, - ) - attached = self.merge(entity, merged) entity_storage = get_entity_storage(entity) @@ -961,21 +965,19 @@ def _merge_recursive(self, entity, merged=None): # Remote attributes. remote_value = entity_storage.get_remote(attribute_name) - if isinstance(remote_value, mergable_types): + if isinstance(remote_value, _MERGABLE_TYPES): log_debug and self.logger.debug( "Merging remote value for attribute {0}.".format(attribute_name) ) - if isinstance(remote_value, ftrack_api.entity.base.Entity): + if isinstance(remote_value, Entity): self._merge_recursive(remote_value, merged=merged) - elif isinstance(remote_value, ftrack_api.collection.Collection): + elif isinstance(remote_value, Collection): for entry in remote_value: self._merge_recursive(entry, merged=merged) - elif isinstance( - remote_value, ftrack_api.collection.MappedCollectionProxy - ): + elif isinstance(remote_value, MappedCollectionProxy): for entry in remote_value.collection: self._merge_recursive(entry, merged=merged) From 0b092a6b936a1916d18107472b1a5f27691013cf Mon Sep 17 00:00:00 2001 From: erhe Date: Thu, 14 Mar 2024 22:14:28 +0100 Subject: [PATCH 6/6] `cache` imports --- source/ftrack_api/cache.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/source/ftrack_api/cache.py b/source/ftrack_api/cache.py index 2ea7ebe4..2c4573c2 100644 --- a/source/ftrack_api/cache.py +++ b/source/ftrack_api/cache.py @@ -48,7 +48,7 @@ import pickle import ftrack_api.inspection -import ftrack_api.symbol +from ftrack_api.symbol import NOT_SET class Cache(with_metaclass(abc.ABCMeta, object)): @@ -184,7 +184,7 @@ def get(self, key): """ target_caches = [] - value = ftrack_api.symbol.NOT_SET + value = NOT_SET for cache in self.caches: try: @@ -195,7 +195,7 @@ def get(self, key): else: break - if value is ftrack_api.symbol.NOT_SET: + if value is NOT_SET: raise KeyError(key) # Set value on all higher level caches.