Caching
The API makes use of caching in order to provide more efficient retrieval of data by reducing the number of calls to the remote server:
# First call to retrieve user performs a request to the server.
user = session.get('User', 'some-user-id')
# A later call in the same session to retrieve the same user just gets
# the existing instance from the cache without a request to the server.
user = session.get('User', 'some-user-id')
It also seamlessly merges related data together regardless of how it was retrieved:
timelog = user['timelogs'][0]
with session.auto_populating(False):
print(timelog['comment'])
NOT_SET
session.query(
'select comment from Timelog where id is "{0}"'
.format(timelog['id'])
).all()
with session.auto_populating(False):
print(timelog['comment'])
'Some comment'
By default, each ~ftrack_api.session.Session
is configured with a simple ~ftrack_api.cache.MemoryCache()
and the cache is lost as soon as the session expires.
Configuring a session cache
It is possible to configure the cache that a session uses. An example would be a persistent auto-populated cache that survives between sessions:
import os
import ftrack_api.cache
# Specify where the file based cache should be stored.
cache_path = os.path.join(tempfile.gettempdir(), 'ftrack_session_cache.dbm')
# Define a cache maker that returns a file based cache. Note that a
# function is used because the file based cache should use the session's
# encode and decode methods to serialise the entity data to a format that
# can be written to disk (JSON).
def cache_maker(session):
'''Return cache to use for *session*.'''
return ftrack_api.cache.SerialisedCache(
ftrack_api.cache.FileCache(cache_path),
encode=session.encode,
decode=session.decode
)
# Create the session using the cache maker.
session = ftrack_api.Session(cache=cache_maker)
There can be a performance penalty when using a more complex cache setup. For example, serialising data and also writing and reading from disk can be relatively slow operations.
Regardless of the cache specified, the session will always construct a ~ftrack_api.cache.LayeredCache
with a ~ftrack_api.cache.MemoryCache
at the top level and then your cache at the second level. This is to ensure consistency of instances returned by the session.
You can check (or even modify) at any time what cache configuration a session is using by accessing the cache
attribute on a ~ftrack_api.session.Session
:
print(session.cache)
`<ftrack_api.cache.LayeredCache object at 0x0000000002F64400>`
Writing a new cache interface
If you have a custom cache backend you should be able to integrate it into the system by writing a cache interface that matches the one defined by ftrack_api.cache.Cache
. This typically involves a subclass and overriding the ~ftrack_api.cache.Cache.get
, ~ftrack_api.cache.Cache.set
and ~ftrack_api.cache.Cache.remove
methods.
Managing what gets cached
The cache system is quite flexible when it comes to controlling what should be cached.
Consider you have a layered cache where the bottom layer cache should be persisted between sessions. In this setup you probably don't want the persisted cache to hold non-persisted values, such as modified entity values or newly created entities not yet committed to the server. However, you might want the top level memory cache to hold onto these values.
Here is one way to set this up. First define a new proxy cache that is selective about what it sets:
import ftrack_api.inspection
class SelectiveCache(ftrack_api.cache.ProxyCache):
'''Proxy cache that won't cache newly created entities.'''
def set(self, key, value):
'''Set *value* for *key*.'''
if isinstance(value, ftrack_api.entity.base.Entity):
if (
ftrack_api.inspection.state(value)
is ftrack_api.symbol.CREATED
):
return
super(SelectiveCache, self).set(key, value)
Now use this custom cache to wrap the serialised cache in the setup above:
def cache_maker(session):
'''Return cache to use for *session*.'''
return SelectiveCache(
ftrack_api.cache.SerialisedCache(
ftrack_api.cache.FileCache(cache_path),
encode=session.encode,
decode=session.decode
)
)
Now to prevent modified attributes also being persisted, tweak the encode settings for the file cache:
import functools
def cache_maker(session):
'''Return cache to use for *session*.'''
return SelectiveCache(
ftrack_api.cache.SerialisedCache(
ftrack_api.cache.FileCache(cache_path),
encode=functools.partial(
session.encode,
entity_attribute_strategy='persisted_only'
),
decode=session.decode
)
)
And use the updated cache maker for your session:
session = ftrack_api.Session(cache=cache_maker)
For some type of attributes that are computed, long term caching is not recommended and such values will not be encoded with the persisted_only
strategy.