Understanding sessions

All communication with an ftrack server takes place through a Session. This allows more opportunity for configuring the connection, plugins etc. and also makes it possible to connect to multiple ftrack servers from within the same Python process.

Connection

A session can be manually configured at runtime to connect to a server with certain credentials:

>>> session = ftrack_api.Session(
...     server_url='http://mycompany.ftrackapp.com',
...     api_key='7545384e-a653-11e1-a82c-f22c11dd25eq',
...     api_user='martin'
... )

Alternatively, a session can use the following environment variables to configure itself:

When using environment variables, no server connection arguments need to be passed manually:

>>> session = ftrack_api.Session()

Unit of work

Each session follows the unit of work pattern. This means that many of the operations performed using a session will happen locally and only be persisted to the server at certain times, notably when calling Session.commit(). This approach helps optimise calls to the server and also group related logic together in a transaction:

user = session.create('User', {})
user['username'] = 'martin'
other_user = session.create('User', {'username': 'bjorn'})
other_user['email'] = 'bjorn@example.com'

Behind the scenes a series of operations are recorded reflecting the changes made. You can take a peek at these operations if desired by examining the Session.recorded_operations property:

>>> for operation in session.recorded_operations:
...     print operation
<ftrack_api.operation.CreateEntityOperation object at 0x0000000003EC49B0>
<ftrack_api.operation.UpdateEntityOperation object at 0x0000000003E16898>
<ftrack_api.operation.CreateEntityOperation object at 0x0000000003E16240>
<ftrack_api.operation.UpdateEntityOperation object at 0x0000000003E16128>

Calling Session.commit() persists all recorded operations to the server and clears the operation log:

session.commit()

Note

The commit call will optimise operations to be as efficient as possible without breaking logical ordering. For example, a create followed by updates on the same entity will be compressed into a single create.

Queries are special and always issued on demand. As a result, a query may return unexpected results if the relevant local changes have not yet been sent to the server:

>>> user = session.create('User', {'username': 'some_unique_username'})
>>> query = 'User where username is "{0}"'.format(user['username'])
>>> print len(session.query(query))
0
>>> session.commit()
>>> print len(session.query(query))
1

Where possible, query results are merged in with existing data transparently with any local changes preserved:

>>> user = session.query('User').first()
>>> user['email'] = 'me@example.com'  # Not yet committed to server.
>>> retrieved = session.query(
...     'User where id is "{0}"'.format(user['id'])
... ).one()
>>> print retrieved['email']  # Displays locally set value.
'me@example.com'
>>> print retrieved is user
True

This is possible due to the smart Caching layer in the session.

Auto-population

Another important concept in a session is that of auto-population. By default a session is configured to auto-populate missing attribute values on access. This means that the first time you access an attribute on an entity instance a query will be sent to the server to fetch the value:

user = session.query('User').first()
# The next command will issue a request to the server to fetch the
# 'username' value on demand at this is the first time it is accessed.
print user['username']

Once a value has been retrieved it is cached locally in the session and accessing it again will not issue more server calls:

# On second access no server call is made.
print user['username']

You can control the auto population behaviour of a session by either changing the Session.auto_populate attribute on a session or using the provided context helper Session.auto_populating() to temporarily change the setting. When turned off you may see a special NOT_SET symbol that represents a value has not yet been fetched:

>>> with session.auto_populating(False):
...     print user['email']
NOT_SET

Whilst convenient for simple scripts, making many requests to the server for each attribute can slow execution of a script. To support optimisation the API includes methods for batch fetching attributes. Read about them in Optimising using projections and Populating entities.

Entity types

When a session has successfully connected to the server it will automatically download schema information and create appropriate classes for use. This is important as different servers can support different entity types and configurations.

This information is readily available and useful if you need to check that the entity types you expect are present. Here’s how to print a list of all entity types registered for use in the current API session:

>>> print session.types.keys()
[u'Task', u'Shot', u'TypedContext', u'Sequence', u'Priority',
 u'Status', u'Project', u'User', u'Type', u'ObjectType']

Each entity type is backed by a customisable class that further describes the entity type and the attributes that are available.

Hint

If you need to use an isinstance() check, always go through the session as the classes are built dynamically:

>>> isinstance(entity, session.types['Project'])

Configuring plugins

As each session is independent of others, you can configure plugins per session.

When starting a new Session either pass the plugins_paths to search explicitly or rely on the environment variable FTRACK_EVENT_PLUGIN_PATH.

If you do not specify any override then the session will attempt to discover and use the default plugins.

Plugins are discovered using ftrack_api.plugin.discover() with the session instance passed as the sole argument. Most plugins should take the form of a mount function that then subscribes to specific events on the session:

def configure_locations(event):
    '''Configure locations for session.'''
    session = event['data']['session']
    # Find location(s) and customise instances.

def register(session):
    '''Register plugin with *session*.'''
    session.event_hub.subscribe(
        'topic=ftrack.api.session.configure-location',
        configure_locations
    )