ftrack Python API

Welcome to the ftrack Python API documentation.

Important

This is the new Python client for the ftrack API. If you are migrating from the old client then please read the dedicated migration guide.

Introduction

This API allows developers to write Python scripts that talk directly with an ftrack server. The scripts can perform operations against that server depending on granted permissions.

With any API it is important to find the right balance between flexibility and usefulness. If an API is too low level then everyone ends up writing boilerplate code for common problems and usually in an non-uniform way making it harder to share scripts with others. It’s also harder to get started with such an API. Conversely, an API that attempts to be too smart can often become restrictive when trying to do more advanced functionality or optimise for performance.

With this API we have tried to strike the right balance between these two, providing an API that should be simple to use out-of-the-box, but also expose more flexibility and power when needed.

Nothing is perfect though, so please do provide feedback on ways that we can continue to improve this API for your specific needs.

Installing

Installation is simple with pip:

pip install ftrack-python-api

Building from source

You can also build manually from the source for more control. First obtain a copy of the source by either downloading the zipball or cloning the public repository:

git clone git@bitbucket.org:ftrack/ftrack-python-api.git

Then you can build and install the package into your current Python site-packages folder:

python setup.py install

Alternatively, just build locally and manage yourself:

python setup.py build

Building documentation from source

To build the documentation from source:

python setup.py build_sphinx

Then view in your browser:

file:///path/to/ftrack-python-api/build/doc/html/index.html

Running tests against the source

With a copy of the source it is also possible to run the unit tests:

python setup.py test

Dependencies

Additional For building

Additional For testing

Tutorial

This tutorial provides a quick dive into using the API and the broad stroke concepts involved.

First make sure the ftrack Python API is installed.

Then start a Python session and import the ftrack API:

>>> import ftrack_api

The API uses sessions to manage communication with an ftrack server. Create a session that connects to your ftrack server (changing the passed values as appropriate):

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

Note

A session can use environment variables to configure itself.

Now print a list of the available entity types retrieved from the server:

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

Now the list of possible entity types is known, query the server to retrieve entities of a particular type by using the Session.query() method:

>>> projects = session.query('Project')

Each project retrieved will be an entity instance that behaves much like a standard Python dictionary. For example, to find out the available keys for an entity, call the keys() method:

>>> print projects[0].keys()
[u'status', u'is_global', u'name', u'end_date', u'context_type',
 u'id', u'full_name', u'root', u'start_date']

Now, iterate over the retrieved entities and print each ones name:

>>> for project in projects:
...     print project['name']
test
client_review
tdb
man_test
ftrack
bunny

Note

Many attributes for retrieved entities are loaded on demand when the attribute is first accessed. Doing this lots of times in a script can be inefficient, so it is worth using projections in queries or pre-populating entities where appropriate. You can also customise default projections to help others pre-load common attributes.

To narrow a search, add criteria to the query:

>>> active_projects = session.query('Project where status is active')

Combine criteria for more powerful queries:

>>> import arrow
>>>
>>> active_projects_ending_before_next_week = session.query(
...     'Project where status is active and end_date before "{0}"'
...     .format(arrow.now().replace(weeks=+1))
... )

Some attributes on an entity will refer to another entity or collection of entities, such as children on a Project being a collection of Context entities that have the project as their parent:

>>> project = session.query('Project').first()
>>> print project['children']
<ftrack_api.collection.Collection object at 0x00000000045B1438>

And on each Context there is a corresponding parent attribute which is a link back to the parent:

>>> child = project['children'][0]
>>> print child['parent'] is project
True

These relationships can also be used in the criteria for a query:

>>> results = session.query(
...     'Context where parent.name like "te%"'
... )

To create new entities in the system use Session.create():

>>> new_sequence = session.create('Sequence', {
...     'name': 'Starlord Reveal'
... })

The created entity is not yet persisted to the server, but it is still possible to modify it.

>>> new_sequence['description'] = 'First hero character reveal.'

The sequence also needs a parent. This can be done in one of two ways:

  • Set the parent attribute on the sequence:

    >>> new_sequence['parent'] = project
    
  • Add the sequence to a parent’s children attribute:

    >>> project['children'].append(new_sequence)
    

When ready, persist to the server using Session.commit():

>>> session.commit()

When finished with a Session, it is important to close() it in order to release resources and properly unsubscribe any registered event listeners. It is also possible to use the session as a context manager in order to have it closed automatically after use:

>>> with ftrack_api.Session() as session:
...     print session.query('User').first()
<User(0154901c-eaf9-11e5-b165-00505681ec7a)>
>>> print session.closed
True

Once a Session is closed, any operations that attempt to use the closed connection to the ftrack server will fail:

>>> session.query('Project').first()
ConnectionClosedError: Connection closed.

Continue to the next section to start learning more about the API in greater depth or jump over to the usage examples if you prefer to learn by example.

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='https://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

Plugins are used by the API to extend it with new functionality, such as locations or adding convenience methods to Entity types. In addition to new API functionality, event plugins may also be used for event processing by listening to ftrack update events or adding custom functionality to ftrack by registering actions.

When starting a new Session either pass the plugins_paths to search explicitly or rely on the environment variable FTRACK_EVENT_PLUGIN_PATH. As each session is independent of others, you can configure plugins per session.

The paths will be searched for plugins, python files which expose a register function. These functions will be evaluated and can be used extend the API with new functionality, such as locations or actions.

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 positional 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
    )

Additional keyword arguments can be passed as plugin_arguments to the Session on instantiation. These are passed to the plugin register function if its signature supports them:

# a_plugin.py
def register(session, reticulate_splines=False):
    '''Register plugin with *session*.'''
    ...

# main.py
session = ftrack_api.Session(
    plugin_arguments={
        'reticulate_splines': True,
        'some_other_argument': 42
    }
)

See also

Lists of events which you can subscribe to in your plugins are available both for synchronous event published by the python API and asynchronous events published by the server

Quick setup

1. Create a directory where plugins will be stored. Place any plugins you want loaded automatically in an API session here.

_images/configuring_plugins_directory.png
  1. Configure the FTRACK_EVENT_PLUGIN_PATH to point to the directory.

Detailed setup

Start out by creating a directory on your machine where you will store your plugins. Download example_plugin.py and place it in the directory.

Open up a terminal window, and ensure that plugin is picked up when instantiating the session and manually setting the plugin_paths:

>>>  # Set up basic logging
>>> import logging
>>> logging.basicConfig()
>>> plugin_logger = logging.getLogger('com.example.example-plugin')
>>> plugin_logger.setLevel(logging.DEBUG)
>>>
>>> # Configure the API, loading plugins in the specified paths.
>>> import ftrack_api
>>> plugin_paths = ['/path/to/plugins']
>>> session = ftrack_api.Session(plugin_paths=plugin_paths)

If everything is working as expected, you should see the following in the output:

DEBUG:com.example.example-plugin:Plugin registered

Instead of specifying the plugin paths when instantiating the session, you can also specify the FTRACK_EVENT_PLUGIN_PATH to point to the directory. To specify multiple directories, use the path separator for your operating system.

Working with entities

Entity instances are Python dict-like objects whose keys correspond to attributes for that type in the system. They may also provide helper methods to perform common operations such as replying to a note:

note = session.query('Note').first()
print note.keys()
print note['content']
note['content'] = 'A different message!'
reply = note.create_reply(...)

Attributes

Each entity instance is typed according to its underlying entity type on the server and configured with appropriate attributes. For example, a task will be represented by a Task class and have corresponding attributes. You can customise entity classes to alter attribute access or provide your own helper methods.

To see the available attribute names on an entity use the keys() method on the instance:

>>> task = session.query('Task').first()
>>> print task.keys()
['id', 'name', ...]

If you need more information about the type of attribute, examine the attributes property on the corresponding class:

>>> for attribute in type(task).attributes:
...     print attribute
<ftrack_api.attribute.ScalarAttribute(id) object at 66701296>
<ftrack_api.attribute.ScalarAttribute(name) object at 66702192>
<ftrack_api.attribute.ReferenceAttribute(status) object at 66701240>
<ftrack_api.attribute.CollectionAttribute(timelogs) object at 66701184>
<ftrack_api.attribute.KeyValueMappedCollectionAttribute(metadata) object at 66701632>
...

Notice that there are different types of attribute such as ScalarAttribute for plain values or ReferenceAttribute for relationships. These different types are reflected in the behaviour on the entity instance when accessing a particular attribute by key:

>>> # Scalar
>>> print task['name']
'model'
>>> task['name'] = 'comp'
>>> # Single reference
>>> print task['status']
<Status(e610b180-4e64-11e1-a500-f23c91df25eb)>
>>> new_status = session.query('Status').first()
>>> task['status'] = new_status
>>> # Collection
>>> print task['timelogs']
<ftrack_api.collection.Collection object at 0x00000000040D95C0>
>>> print task['timelogs'][:]
[<dynamic ftrack Timelog object 72322240>, ...]
>>> new_timelog = session.create('Timelog', {...})
>>> task['timelogs'].append(new_timelog)

Bi-directional relationships

Some attributes refer to different sides of a bi-directional relationship. In the current version of the API bi-directional updates are not propagated automatically to the other side of the relationship. For example, setting a parent will not update the parent entity’s children collection locally. There are plans to support this behaviour better in the future. For now, after commit, populate the reverse side attribute manually.

Creating entities

In order to create a new instance of an entity call Session.create() passing in the entity type to create and any initial attribute values:

new_user = session.create('User', {'username': 'martin'})

If there are any default values that can be set client side then they will be applied at this point. Typically this will be the unique entity key:

>>> print new_user['id']
170f02a4-6656-4f15-a5cb-c4dd77ce0540

At this point no information has been sent to the server. However, you are free to continue updating this object locally until you are ready to persist the changes by calling Session.commit().

If you are wondering about what would happen if you accessed an unset attribute on a newly created entity, go ahead and give it a go:

>>> print new_user['first_name']
NOT_SET

The session knows that it is a newly created entity that has not yet been persisted so it doesn’t try to fetch any attributes on access even when session.auto_populate is turned on.

Updating entities

Updating an entity is as simple as modifying the values for specific keys on the dict-like instance and calling Session.commit() when ready. The entity to update can either be a new entity or a retrieved entity:

task = session.query('Task').first()
task['bid'] = 8

Remember that, for existing entities, accessing an attribute will load it from the server automatically. If you are interested in just setting values without first fetching them from the server, turn auto-population off temporarily:

>>> with session.auto_populating(False):
...    task = session.query('Task').first()
...    task['bid'] = 8

Server side reset of entity attributes or settings.

Some entities support resetting of attributes, for example to reset a users api key:

session.reset_remote(
    'api_key', entity=session.query('User where username is "test_user"').one()
)

Note

Currently the only attribute possible to reset is ‘api_key’ on the user entity type.

Deleting entities

To delete an entity you need an instance of the entity in your session (either from having created one or retrieving one). Then call Session.delete() on the entity and Session.commit() when ready:

task_to_delete = session.query('Task').first()
session.delete(task_to_delete)
...
session.commit()

Note

Even though the entity is deleted, you will still have access to the local instance and any local data stored on that instance whilst that instance remains in memory.

Keep in mind that some deletions, when propagated to the server, will cause other entities to be deleted also, so you don’t have to worry about deleting an entire hierarchy manually. For example, deleting a Task will also delete all Notes on that task.

Populating entities

When an entity is retrieved via Session.query() or Session.get() it will have some attributes prepopulated. The rest are dynamically loaded when they are accessed. If you need to access many attributes it can be more efficient to request all those attributes be loaded in one go. One way to do this is to use a projections in queries.

However, if you have entities that have been passed to you from elsewhere you don’t have control over the query that was issued to get those entities. In this case you can you can populate those entities in one go using Session.populate() which works exactly like projections in queries do, but operating against known entities:

>>> users = session.query('User')
>>> session.populate(users, 'first_name, last_name')
>>> with session.auto_populating(False):  # Turn off for example purpose.
...     for user in users:
...         print 'Name: {0}'.format(user['first_name'])
...         print 'Email: {0}'.format(user['email'])
Name: Martin
Email: NOT_SET
...

Note

You can populate a single or many entities in one call so long as they are all the same entity type.

Entity states

Operations on entities are recorded in the session as they happen. At any time you can inspect an entity to determine its current state from those pending operations.

To do this, use ftrack_api.inspection.state():

>>> import ftrack_api.inspection
>>> new_user = session.create('User', {})
>>> print ftrack_api.inspection.state(new_user)
CREATED
>>> existing_user = session.query('User').first()
>>> print ftrack_api.inspection.state(existing_user)
NOT_SET
>>> existing_user['email'] = 'martin@example.com'
>>> print ftrack_api.inspection.state(existing_user)
MODIFIED
>>> session.delete(new_user)
>>> print ftrack_api.inspection.state(new_user)
DELETED

Customising entity types

Each type of entity in the system is represented in the Python client by a dedicated class. However, because the types of entities can vary these classes are built on demand using schema information retrieved from the server.

Many of the default classes provide additional helper methods which are mixed into the generated class at runtime when a session is started.

In some cases it can be useful to tailor the custom classes to your own pipeline workflows. Perhaps you want to add more helper functions, change attribute access rules or even providing a layer of backwards compatibility for existing code. The Python client was built with this in mind and makes such customisations as easy as possible.

When a Session is constructed it fetches schema details from the connected server and then calls an Entity factory to create classes from those schemas. It does this by emitting a synchronous event, ftrack.api.session.construct-entity-type, for each schema and expecting a class object to be returned.

In the default setup, a construct_entity_type.py plugin is placed on the FTRACK_EVENT_PLUGIN_PATH. This plugin will register a trivial subclass of ftrack_api.entity.factory.StandardFactory to create the classes in response to the construct event. The simplest way to get started is to edit this default plugin as required.

Default projections

When a query is issued without any projections, the session will automatically add default projections according to the type of the entity.

For example, the following shows that for a User, only id is fetched by default when no projections added to the query:

>>> user = session.query('User').first()
>>> with session.auto_populating(False):  # For demonstration purpose only.
...     print user.items()
[
    (u'id', u'59f0963a-15e2-11e1-a5f1-0019bb4983d8')
    (u'username', Symbol(NOT_SET)),
    (u'first_name', Symbol(NOT_SET)),
    ...
]

Note

These default projections are also used when you access a relationship attribute using the dictionary key syntax.

If you want to default to fetching username for a Task as well then you can change the default_projections* in your class factory plugin:

class Factory(ftrack_api.entity.factory.StandardFactory):
    '''Entity class factory.'''

    def create(self, schema, bases=None):
        '''Create and return entity class from *schema*.'''
        cls = super(Factory, self).create(schema, bases=bases)

        # Further customise cls before returning.
        if schema['id'] == 'User':
            cls.default_projections = ['id', 'username']

        return cls

Now a projection-less query will also query username by default:

Note

You will need to start a new session to pick up the change you made:

session = ftrack_api.Session()
>>> user = session.query('User').first()
>>> with session.auto_populating(False):  # For demonstration purpose only.
...     print user.items()
[
    (u'id', u'59f0963a-15e2-11e1-a5f1-0019bb4983d8')
    (u'username', u'martin'),
    (u'first_name', Symbol(NOT_SET)),
    ...
]

Note that if any specific projections are applied in a query, those override the default projections entirely. This allows you to also reduce the data loaded on demand:

>>> session = ftrack_api.Session()  # Start new session to avoid cache.
>>> user = session.query('select id from User').first()
>>> with session.auto_populating(False):  # For demonstration purpose only.
...     print user.items()
[
    (u'id', u'59f0963a-15e2-11e1-a5f1-0019bb4983d8')
    (u'username', Symbol(NOT_SET)),
    (u'first_name', Symbol(NOT_SET)),
    ...
]

Helper methods

If you want to add additional helper methods to the constructed classes to better support your pipeline logic, then you can simply patch the created classes in your factory, much like with changing the default projections:

def get_full_name(self):
    '''Return full name for user.'''
    return '{0} {1}'.format(self['first_name'], self['last_name']).strip()

class Factory(ftrack_api.entity.factory.StandardFactory):
    '''Entity class factory.'''

    def create(self, schema, bases=None):
        '''Create and return entity class from *schema*.'''
        cls = super(Factory, self).create(schema, bases=bases)

        # Further customise cls before returning.
        if schema['id'] == 'User':
            cls.get_full_name = get_full_name

        return cls

Now you have a new helper method get_full_name on your User entities:

>>> session = ftrack_api.Session()  # New session to pick up changes.
>>> user = session.query('User').first()
>>> print user.get_full_name()
Martin Pengelly-Phillips

If you’d rather not patch the existing classes, or perhaps have a lot of helpers to mixin, you can instead inject your own class as the base class. The only requirement is that it has the base Entity class in its ancestor classes:

import ftrack_api.entity.base


class CustomUser(ftrack_api.entity.base.Entity):
    '''Represent user.'''

    def get_full_name(self):
        '''Return full name for user.'''
        return '{0} {1}'.format(self['first_name'], self['last_name']).strip()


class Factory(ftrack_api.entity.factory.StandardFactory):
    '''Entity class factory.'''

    def create(self, schema, bases=None):
        '''Create and return entity class from *schema*.'''
        # Alter base class for constructed class.
        if bases is None:
            bases = [ftrack_api.entity.base.Entity]

        if schema['id'] == 'User':
            bases = [CustomUser]

        cls = super(Factory, self).create(schema, bases=bases)
        return cls

The resulting effect is the same:

>>> session = ftrack_api.Session()  # New session to pick up changes.
>>> user = session.query('User').first()
>>> print user.get_full_name()
Martin Pengelly-Phillips

Note

Your custom class is not the leaf class which will still be a dynamically generated class. Instead your custom class becomes the base for the leaf class:

>>> print type(user).__mro__
(<dynamic ftrack class 'User'>, <dynamic ftrack class 'CustomUser'>, ...)

Querying

The API provides a simple, but powerful query language in addition to iterating directly over entity attributes. Using queries can often substantially speed up your code as well as reduce the amount of code written.

A query is issued using Session.query() and returns a list of matching entities. The query always has a single target entity type that the query is built against. This means that you cannot currently retrieve back a list of different entity types in one query, though using projections does allow retrieving related entities of a different type in one go.

The syntax for a query is:

select <projections> from <entity type> where <criteria>

However, both the selection of projections and criteria are optional. This means the most basic query is just to fetch all entities of a particular type, such as all projects in the system:

projects = session.query('Project')

A query always returns a QueryResult instance that acts like a list with some special behaviour. The main special behaviour is that the actual query to the server is not issued until you iterate or index into the query results:

for project in projects:
    print project['name']

You can also explicitly call all() on the result set:

projects = session.query('Project').all()

Note

This behaviour exists in order to make way for efficient paging and other optimisations in future.

Using criteria to narrow results

Often you will have some idea of the entities you want to retrieve. In this case you can optimise your code by not fetching more data than you need. To do this, add criteria to your query:

projects = session.query('Project where status is active')

Each criteria follows the form:

<attribute> <operator> <value>

You can inspect the entity type or instance to find out which attributes are available to filter on for a particular entity type. The list of operators that can be applied and the types of values they expect is listed later on.

Combining criteria

Multiple criteria can be applied in a single expression by joining them with either and or or:

projects = session.query(
    'Project where status is active and name like "%thrones"'
)

You can use parenthesis to control the precedence when compound criteria are used (by default and takes precedence):

projects = session.query(
    'Project where status is active and '
    '(name like "%thrones" or full_name like "%thrones")'
)

Filtering on relationships

Filtering on relationships is also intuitively supported. Simply follow the relationship using a dotted notation:

tasks_in_project = session.query(
    'Task where project.id is "{0}"'.format(project['id'])
)

This works even for multiple strides across relationships (though do note that excessive strides can affect performance):

tasks_completed_in_project = session.query(
    'Task where project.id is "{0}" and '
    'status.type.name is "Done"'
    .format(project['id'])
)

The same works for collections (where each entity in the collection is compared against the subsequent condition):

import arrow

tasks_with_time_logged_today = session.query(
    'Task where timelogs.start >= "{0}"'.format(arrow.now().floor('day'))
)

In the above query, each Task that has at least one Timelog with a start time greater than the start of today is returned.

When filtering on relationships, the conjunctions has and any can be used to specify how the criteria should be applied. This becomes important when querying using multiple conditions on collection relationships. The relationship condition can be written against the following form:

<not?> <relationship> <has|any> (<criteria>)

For optimal performance has should be used for scalar relationships when multiple conditions are involved. For example, to find notes by a specific author when only name is known:

notes_written_by_jane_doe = session.query(
    'Note where author has (first_name is "Jane" and last_name is "Doe")'
)

This query could be written without has, giving the same results:

notes_written_by_jane_doe = session.query(
    'Note where author.first_name is "Jane" and author.last_name is "Doe"'
)

any should be used for collection relationships. For example, to find all projects that have at least one metadata instance that has key=some_key and value=some_value the query would be:

projects_where_some_key_is_some_value = session.query(
    'Project where metadata any (key=some_key and value=some_value)'
)

If the query was written without any, projects with one metadata matching key and another matching the value would be returned.

any can also be used to query for empty relationship collections:

users_without_timelogs = session.query(
    'User where not timelogs any ()'
)

Supported operators

This is the list of currently supported operators:

Operators Description Example
= is Exactly equal. name is “martin”
!= is_not Not exactly equal. name is_not “martin”
> after greater_than Greater than exclusive. start after “2015-06-01”
< before less_than Less than exclusive. end before “2015-06-01”
>= Greater than inclusive. bid >= 10
<= Less than inclusive. bid <= 10
in One of. status.type.name in (“In Progress”, “Done”)
not_in Not one of. status.name not_in (“Omitted”, “On Hold”)
like Matches pattern. name like “%thrones”
not_like Does not match pattern. name not_like “%thrones”
has Test scalar relationship. author has (first_name is “Jane” and last_name is “Doe”)
any Test collection relationship. metadata any (key=some_key and value=some_value)

Optimising using projections

In Understanding sessions we mentioned auto-population of attribute values on access. This meant that when iterating over a lot of entities and attributes a large number of queries were being sent to the server. Ultimately, this can cause your code to run slowly:

>>> projects = session.query('Project')
>>> for project in projects:
...     print(
...         # Multiple queries issued here for each attribute accessed for
...         # each project in the loop!
...         '{project[full_name]} - {project[status][name]})'
...         .format(project=project)
...     )

Fortunately, there is an easy way to optimise. If you know what attributes you are interested in ahead of time you can include them in your query string as projections in order to fetch them in one go:

>>> projects = session.query(
...     'select full_name, status.name from Project'
... )
>>> for project in projects:
...     print(
...         # No additional queries issued here as the values were already
...         # loaded by the above query!
...         '{project[full_name]} - {project[status][name]})'
...         .format(project=project)
...     )

Notice how this works for related entities as well. In the example above, we also fetched the name of each Status entity attached to a project in the same query, which meant that no further queries had to be issued when accessing those nested attributes.

Note

There are no arbitrary limits to the number (or depth) of projections, but do be aware that excessive projections can ultimately result in poor performance also. As always, it is about choosing the right tool for the job.

You can also customise the Default projections to use for each entity type when none are specified in the query string.

Handling events

Events are generated in ftrack when things happen such as a task being updated or a new version being published. Each Session automatically connects to the event server and can be used to subscribe to specific events and perform an action as a result. That action could be updating another related entity based on a status change or generating folders when a new shot is created for example.

The EventHub for each Session is accessible via Session.event_hub.

Subscribing to events

To listen to events, you register a function against a subscription using Session.event_hub.subscribe. The subscription uses the expression syntax and will filter against each Event instance to determine if the registered function should receive that event. If the subscription matches, the registered function will be called with the Event instance as its sole argument. The Event instance is a mapping like structure and can be used like a normal dictionary.

The following example subscribes a function to receive all ‘ftrack.update’ events and then print out the entities that were updated:

import ftrack_api


def my_callback(event):
    '''Event callback printing all new or updated entities.'''
    for entity in event['data'].get('entities', []):

        # Print data for the entity.
        print(entity)


# Subscribe to events with the update topic.
session = ftrack_api.Session()
session.event_hub.subscribe('topic=ftrack.update', my_callback)

At this point, if you run this, your code would exit almost immediately. This is because the event hub listens for events in a background thread. Typically, you only want to stay connected whilst using the session, but in some cases you will want to block and listen for events solely - a dedicated event processor. To do this, use the EventHub.wait method:

# Wait for events to be received and handled.
session.event_hub.wait()

You cancel waiting for events by using a system interrupt (Ctrl-C). Alternatively, you can specify a duration to process events for:

# Only wait and process events for 5 seconds.
session.event_hub.wait(duration=5)

Note

Events are continually received and queued for processing in the background as soon as the connection to the server is established. As a result you may see a flurry of activity as soon as you call wait() for the first time.

Subscriber information

When subscribing, you can also specify additional information about your subscriber. This contextual information can be useful when routing events, particularly when targeting events. By default, the EventHub will set some default information, but it can be useful to enhance this. To do so, simply pass in subscriber as a dictionary of data to the subscribe() method:

session.event_hub.subscribe(
    'topic=ftrack.update',
    my_callback,
    subscriber={
        'id': 'my-unique-subscriber-id',
        'applicationId': 'maya'
    }
)

Sending replies

When handling an event it is sometimes useful to be able to send information back to the source of the event. For example, ftrack.location.request-resolve would expect a resolved path to be sent back.

You can craft a custom reply event if you want, but an easier way is just to return the appropriate data from your handler. Any non None value will be automatically sent as a reply:

def on_event(event):
    # Send following data in automatic reply.
    return {'success': True, 'message': 'Cool!'}

session.event_hub.subscribe('topic=test-reply', on_event)

See also

Handling replies

Note

Some events are published synchronously. In this case, any returned data is passed back to the publisher directly.

Stopping events

The event instance passed to each event handler also provides a method for stopping the event, Event.stop.

Once an event has been stopped, no further handlers for that specific event will be called locally. Other handlers in other processes may still be called.

Combining this with setting appropriate priorities when subscribing to a topic allows handlers to prevent lower priority handlers running when desired.

>>> import ftrack_api
>>> import ftrack_api.event.base
>>>
>>> def callback_a(event):
...     '''Stop the event!'''
...     print('Callback A')
...     event.stop()
>>>
>>> def callback_b(event):
...     '''Never run.'''
...     print('Callback B')
>>>
>>> session = ftrack_api.Session()
>>> session.event_hub.subscribe(
...     'topic=test-stop-event', callback_a, priority=10
... )
>>> session.event_hub.subscribe(
...     'topic=test-stop-event', callback_b, priority=20
... )
>>> session.event_hub.publish(
...     ftrack_api.event.base.Event(topic='test-stop-event')
... )
>>> session.event_hub.wait(duration=5)
Callback A called.

Publishing events

So far we have looked at listening to events coming from ftrack. However, you are also free to publish your own events (or even publish relevant ftrack events).

To do this, simply construct an instance of ftrack_api.event.base.Event and pass it to EventHub.publish via the session:

import ftrack_api.event.base

event = ftrack_api.event.base.Event(
    topic='my-company.some-topic',
    data={'key': 'value'}
)
session.event_hub.publish(event)

The event hub will automatically add some information to your event before it gets published, including the source of the event. By default the event source is just the event hub, but you can customise this to provide more relevant information if you want. For example, if you were publishing from within Maya:

session.event_hub.publish(ftrack_api.event.base.Event(
    topic='my-company.some-topic',
    data={'key': 'value'},
    source={
        'applicationId': 'maya'
    }
))

Remember that all supplied information can be used by subscribers to filter events so the more accurate the information the better.

Publish synchronously

It is also possible to call publish() synchronously by passing synchronous=True. In synchronous mode, only local handlers will be called. The result from each called handler is collected and all the results returned together in a list:

>>> import ftrack_api
>>> import ftrack_api.event.base
>>>
>>> def callback_a(event):
...     return 'A'
>>>
>>> def callback_b(event):
...     return 'B'
>>>
>>> session = ftrack_api.Session()
>>> session.event_hub.subscribe(
...     'topic=test-synchronous', callback_a, priority=10
... )
>>> session.event_hub.subscribe(
...     'topic=test-synchronous', callback_b, priority=20
... )
>>> results = session.event_hub.publish(
...     ftrack_api.event.base.Event(topic='test-synchronous'),
...     synchronous=True
... )
>>> print results
['A', 'B']

Handling replies

When publishing an event it is also possible to pass a callable that will be called with any reply event received in response to the published event.

To do so, simply pass in a callable as the on_reply parameter:

def handle_reply(event):
    print 'Got reply', event

session.event_hub.publish(
    ftrack_api.event.base.Event(topic='test-reply'),
    on_reply=handle_reply
)

Targeting events

In addition to subscribers filtering events to receive, it is also possible to give an event a specific target to help route it to the right subscriber.

To do this, set the target value on the event to an expression. The expression will filter against registered subscriber information.

For example, if you have many subscribers listening for a event, but only want one of those subscribers to get the event, you can target the event to the subscriber using its registered subscriber id:

session.event_hub.publish(
    ftrack_api.event.base.Event(
        topic='my-company.topic',
        data={'key': 'value'},
        target='id=my-custom-subscriber-id'
    )
)

Expressions

An expression is used to filter against a data structure, returning whether the structure fulfils the expression requirements. Expressions are currently used for subscriptions when subscribing to events and for targets when publishing targeted events.

The form of the expression is loosely groupings of ‘key=value’ with conjunctions to join them.

For example, a common expression for subscriptions is to filter against an event topic:

'topic=ftrack.location.component-added'

However, you can also perform more complex filtering, including accessing nested parameters:

'topic=ftrack.location.component-added and data.locationId=london'

Note

If the structure being tested does not have any value for the specified key reference then it is treated as not matching.

You can also use a single wildcard ‘*’ at the end of any value for matching multiple values. For example, the following would match all events that have a topic starting with ‘ftrack.’:

'topic=ftrack.*'

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 Session is configured with a simple 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)

Note

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 LayeredCache with a 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 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 get(), set() and 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)

Note

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.

Locations

Learn how to access locations using the API and configure your own location plugins.

Overview

Locations provides a way to easily track and manage data (files, image sequences etc.) using ftrack.

With locations it is possible to see where published data is in the world and also to transfer data automatically between different locations, even different storage mechanisms, by defining a few simple Python plugins. By keeping track of the size of the data it also helps manage storage capacity better. In addition, the intrinsic links to production information makes assigning work to others and transferring only the relevant data much simpler as well as greatly reducing the burden on those responsible for archiving finished work.

Concepts

The system is implemented in layers using a few key concepts in order to provide a balance between out of the box functionality and custom configuration.

Locations

Data locations can be varied in scope and meaning - a facility, a laptop, a specific drive. As such, rather than place a hard limit on what can be considered a location, ftrack simply requires that a location be identifiable by a string and that string be unique to that location.

A global company with facilities in many different parts of the world might follow a location naming convention similar to the following:

  • ‘ftrack.london.server01’
  • ‘ftrack.london.server02’
  • ‘ftrack.nyc.server01’
  • ‘ftrack.amsterdam.server01’
  • ‘<company>.<city>.<server#>’

Whereas, for a looser setup, the following might suit better:

  • ‘bjorns-workstation’
  • ‘fredriks-mobile’
  • ‘martins-laptop’
  • ‘cloud-backup’
Availability

When tracking data across several locations it is important to be able to quickly find out where data is available and where it is not. As such, ftrack provides simple mechanisms for retrieving information on the availability of a component in each location.

For a single file, the availability with be either 0% or 100%. For containers, such as file sequences, each file is tracked separately and the availability of the container calculated as an overall percentage (e.g. 47%).

Accessors

Due to the flexibility of what can be considered a location, the system must be able to cope with locations that represent different ways of storing data. For example, data might be stored on a local hard drive, a cloud service or even in a database.

In addition, the method of accessing that storage can change depending on perspective - local filesystem, FTP, S3 API etc.

To handle this, ftrack introduces the idea of an accessor that provides access to the data in a standard way. An accessor is implemented in Python following a set interface and can be configured at runtime to provide relevant access to a location.

With an accessor configured for a location, it becomes possible to not only track data, but also manage it through ftrack by using the accessor to add and remove data from the location.

At present, ftrack includes a disk accessor for local filesystem access. More will be added over time and developers are encouraged to contribute their own.

Structure

Another important consideration for locations is how data should be structured in the location (folder structure and naming conventions). For example, different facilities may want to use different folder structures, or different storage mechanisms may use different paths for the data.

For this, ftrack supports the use of a Python structure plugin. This plugin is called when adding a component to a location in order to determine the correct structure to use.

Note

A structure plugin accepts an ftrack entity as its input and so can be reused for generating general structures as well. For example, an action callback could be implemented to create the base folder structure for some selected shots by reusing a structure plugin.

Resource identifiers

When a component can be linked to multiple locations it becomes necessary to store information about the relationship on the link rather than directly on the component itself. The most important information is the path to the data in that location.

However, as seen above, not all locations may be filesystem based or accessed using standard filesystem protocols. For this reason, and to help avoid confusion, this path is referred to as a resource identifier and no limitations are placed on the format. Keep in mind though that accessors use this information (retrieved from the database) in order to work out how to access the data, so the format used must be compatible with all the accessors used for any one location. For this reason, most resource identifiers should ideally look like relative filesystem paths.

Transformer

To further support custom formats for resource identifiers, it is also possible to configure a resource identifier transformer plugin which will convert the identifiers before they are stored centrally and after they are retrieved.

A possible use case of this might be to store JSON encoded metadata about a path in the database and convert this to an actual filesystem path on retrieval.

Tutorial

This tutorial is a walkthrough on how you interact with Locations using the ftrack API. Before you read this tutorial, make sure you familiarize yourself with the location concepts by reading the Overview.

All examples assume you are using Python 2.x, have the ftrack_api module imported and a session created.

import ftrack_api
session = ftrack_api.Session()

Creating locations

Locations can be created just like any other entity using Session.create:

location = session.create('Location', dict(name='my.location'))
session.commit()

Note

Location names beginning with ftrack. are reserved for internal use. Do not use this prefix for your location names.

To create a location only if it doesn’t already exist use the convenience method Session.ensure. This will return either an existing matching location or a newly created one.

Retrieving locations

You can retrieve existing locations using the standard session get() and query() methods:

# Retrieve location by unique id.
location_by_id = session.get('Location', 'unique-id')

# Retrieve location by name.
location_by_name = session.query(
    'Location where name is "my.location"'
).one()

To retrieve all existing locations use a standard query:

all_locations = session.query('Location').all()
for existing_location in all_locations:
    print existing_location['name']

Configuring locations

At this point you have created a custom location “my.location” in the database and have an instance to reflect that. However, the location cannot be used in this session to manage data unless it has been configured. To configure a location for the session, set the appropriate attributes for accessor and structure:

import tempfile
import ftrack_api.accessor.disk
import ftrack_api.structure.id

# Assign a disk accessor with *temporary* storage
location.accessor = ftrack_api.accessor.disk.DiskAccessor(
    prefix=tempfile.mkdtemp()
)

# Assign using ID structure.
location.structure = ftrack_api.structure.id.IdStructure()

# Set a priority which will be used when automatically picking locations.
# Lower number is higher priority.
location.priority = 30

To learn more about how to configure locations automatically in a session, see Configuring locations.

Note

If a location is not configured in a session it can still be used as a standard entity and to find out availability of components

Using components with locations

The Locations API tries to use sane defaults to stay out of your way. When creating components, a location is automatically picked using Session.pick_location:

(_, component_path) = tempfile.mkstemp(suffix='.txt')
component_a = session.create_component(path=component_path)

To override, specify a location explicitly:

(_, component_path) = tempfile.mkstemp(suffix='.txt')
component_b = session.create_component(
    path=component_path, location=location
)

If you set the location to None, the component will only be present in the special origin location for the duration of the session:

(_, component_path) = tempfile.mkstemp(suffix='.txt')
component_c = session.create_component(path=component_path, location=None)

After creating a component in a location, it can be added to another location by calling Location.add_component and passing the location to use as the source location:

origin_location = session.query(
    'Location where name is "ftrack.origin"'
).one()
location.add_component(component_c, origin_location)

To remove a component from a location use Location.remove_component:

location.remove_component(component_b)

Each location specifies whether to automatically manage data when adding or removing components. To ensure that a location does not manage data, mixin the relevant location mixin class before use:

import ftrack_api
import ftrack_api.entity.location

ftrack_api.mixin(location, ftrack_api.entity.location.UnmanagedLocationMixin)

Accessing paths

The locations system is designed to help avoid having to deal with filesystem paths directly. This is particularly important when you consider that a number of locations won’t provide any direct filesystem access (such as cloud storage).

However, it is useful to still be able to get a filesystem path from locations that support them (typically those configured with a DiskAccessor). For example, you might need to pass a filesystem path to another application or perform a copy using a faster protocol.

To retrieve the path if available, use Location.get_filesystem_path:

print location.get_filesystem_path(component_c)

Obtaining component availability

Components in locations have a notion of availability. For regular components, consisting of a single file, the availability would be either 0 if the component is unavailable or 100 percent if the component is available in the location. Composite components, like image sequences, have an availability which is proportional to the amount of child components that have been added to the location.

For example, an image sequence might currently be in a state of being transferred to test.location. If half of the images are transferred, it might be possible to start working with the sequence. To check availability use the helper Session.get_component_availability method:

print session.get_component_availability(component_c)

There are also convenience methods on both components and locations for retrieving availability as well:

print component_c.get_availability()
print location.get_component_availability(component_c)

Location events

If you want to receive event notifications when components are added to or removed from locations, you can subscribe to the topics published, ftrack_api.symbol.COMPONENT_ADDED_TO_LOCATION_TOPIC or ftrack_api.symbol.COMPONENT_REMOVED_FROM_LOCATION_TOPIC and the callback you want to be run.

Configuring locations

To allow management of data by a location or retrieval of filesystem paths where supported, a location instance needs to be configured in a session with an accessor and structure.

Note

The standard builtin locations require no further setup or configuration and it is not necessary to read the rest of this section to use them.

Before continuing, make sure that you are familiar with the general concepts of locations by reading the Overview.

Configuring manually

Locations can be configured manually when using a session by retrieving the location and setting the appropriate attributes:

location = session.query('Location where name is "my.location"').one()
location.structure = ftrack_api.structure.id.IdStructure()
location.priority = 50

Configuring automatically

Often the configuration of locations should be determined by developers looking after the core pipeline and so ftrack provides a way for a plugin to be registered to configure the necessary locations for each session. This can then be managed centrally if desired.

The configuration is handled through the standard events system via a topic ftrack.api.session.configure-location. Set up an event listener plugin as normal with a register function that accepts a Session instance. Then register a callback against the relevant topic to configure locations at the appropriate time:

import ftrack_api
import ftrack_api.entity.location
import ftrack_api.accessor.disk
import ftrack_api.structure.id


def configure_locations(event):
    '''Configure locations for session.'''
    session = event['data']['session']

    # Find location(s) and customise instances.
    location = session.query('Location where name is "my.location"').one()
    ftrack_api.mixin(location, ftrack_api.entity.location.UnmanagedLocationMixin)
    location.accessor = ftrack_api.accessor.disk.DiskAccessor(prefix='')
    location.structure = ftrack_api.structure.id.IdStructure()
    location.priority = 50


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

Note

If you expect the plugin to also be evaluated by the legacy API, remember to validate the arguments.

So long as the directory containing the plugin exists on your FTRACK_EVENT_PLUGIN_PATH, the plugin will run for each session created and any configured locations will then remain configured for the duration of that related session.

Be aware that you can configure many locations in one plugin or have separate plugins for different locations - the choice is entirely up to you!

Usage examples

The following examples show how to use the API to accomplish specific tasks using the default configuration.

Note

If you are using a server with a customised configuration you may need to alter the examples slightly to make them work correctly.

Most of the examples assume you have the ftrack_api package imported and have already constructed a Session:

import ftrack_api

session = ftrack_api.Session()

Working with projects

Creating a project

A project with sequences, shots and tasks can be created in one single transaction. Tasks need to have a type and status set on creation based on the project schema:

import uuid

# Create a unique name for the project.
name = 'projectname_{0}'.format(uuid.uuid1().hex)

# Naively pick the first project schema. For this example to work the
# schema must contain `Shot` and `Sequence` object types.
project_schema = session.query('ProjectSchema').first()

# Create the project with the chosen schema.
project = session.create('Project', {
    'name': name,
    'full_name': name + '_full',
    'project_schema': project_schema
})

# Retrieve default types.
default_shot_status = project_schema.get_statuses('Shot')[0]
default_task_type = project_schema.get_types('Task')[0]
default_task_status = project_schema.get_statuses(
    'Task', default_task_type['id']
)[0]

# Create sequences, shots and tasks.
for sequence_number in range(1, 5):
    sequence = session.create('Sequence', {
        'name': 'seq_{0}'.format(sequence_number),
        'parent': project
    })

    for shot_number in range(1, 5):
        shot = session.create('Shot', {
            'name': '{0}0'.format(shot_number).zfill(3),
            'parent': sequence,
            'status': default_shot_status
        })

        for task_number in range(1, 5):
            session.create('Task', {
                'name': 'task_{0}'.format(task_number),
                'parent': shot,
                'status': default_task_status,
                'type': default_task_type
            })

# Commit all changes to the server.
session.commit()

Working with components

Components can be created manually or using the provide helper methods on a session or existing asset version:

component = version.create_component('/path/to/file_or_sequence.jpg')
session.commit()

When a component is created using the helpers it is automatically added to a location.

Using review sessions

Client review sessions can either be queried manually or by using a project instance.

review_sessions = session.query(
    'ReviewSession where name is "Weekly review"'
)

project_review_sessions = project['review_sessions']

To create a new review session on a specific project use Session.create().

review_session = session.create('ReviewSession', {
    'name': 'Weekly review',
    'description': 'See updates from last week.',
    'project': project
})

To add objects to a review session create them using Session.create() and reference a review session and an asset version.

review_session = session.create('ReviewSessionObject', {
    'name': 'Compositing',
    'description': 'Fixed shadows.',
    'version': 'Version 3',
    'review_session': review_session,
    'asset_version': asset_version
})

To list all objects in a review session.

review_session_objects = review_session['review_session_objects']

Listing and adding collaborators to review session can be done using Session.create() and the review_session_invitees relation on a review session.

invitee = session.create('ReviewSessionInvitee', {
    'name': 'John Doe',
    'email': 'john.doe@example.com',
    'review_session': review_session
})

session.commit()

invitees = review_session['review_session_invitees']

To remove a collaborator simply delete the object using Session.delete().

session.delete(invitee)

To send out an invite email to a signle collaborator use Session.send_review_session_invite().

session.send_review_session_invite(invitee)

Multiple invitees can have emails sent to them in one batch using Session.send_review_session_invites().

session.send_review_session_invites(a_list_of_invitees)

Using metadata

Key/value metadata can be written to entities using the metadata property and also used to query entities.

The metadata property has a similar interface as a dictionary and keys can be printed using the keys method:

>>> print new_sequence['metadata'].keys()
['frame_padding', 'focal_length']

or items:

>>> print new_sequence['metadata'].items()
[('frame_padding': '4'), ('focal_length': '70')]

Read existing metadata:

>>> print new_sequence['metadata']['frame_padding']
'4'

Setting metadata can be done in a few ways where that later one will replace any existing metadata:

new_sequence['metadata']['frame_padding'] = '5'
new_sequence['metadata'] = {
    'frame_padding': '4'
}

Entities can also be queried using metadata:

session.query(
    'Sequence where metadata any (key is "frame_padding" and value is "4")'
)

Using custom attributes

Custom attributes can be written and read from entities using the custom_attributes property.

The custom_attributes property provides a similar interface to a dictionary.

Keys can be printed using the keys method:

>>> task['custom_attributes'].keys()
[u'my_text_field']

or access keys and values as items:

>>> print task['custom_attributes'].items()
[(u'my_text_field', u'some text')]

Read existing custom attribute values:

>>> print task['custom_attributes']['my_text_field']
'some text'

Updating a custom attributes can also be done similar to a dictionary:

task['custom_attributes']['my_text_field'] = 'foo'

To query for tasks with a custom attribute, my_text_field, you can use the key from the configuration:

for task in session.query(
    'Task where custom_attributes any '
    '(key is "my_text_field" and value is "bar")'
):
    print task['name']

Limitations

Set attributes on new entities

There are some limitations that are important to be aware of when creating an entity and updating custom attributes in one commit.

The following code does not work:

task = session.create('Task', {
    ...
    'custom_attributes': {
        'my_text_field': 'bar',
    },
})
session.commit()

Instead, the entity must be created first, then we can set the custom attributes:

task = session.create('Task', {
    ...
})
task['custom_attributes']['my_text_field'] = 'bar'
session.commit()

After the commit the remote value is not automatically populated. This will cause an extra query to the server when a custom attribute is accessed:

# This will cause a server query.
print task['custom_attributes']['my_text_field']
Expression attributes

Expression attributes are not yet supported and the reported value will always be the non-evaluated expression.

Hierarchical attributes

Hierarchical attributes are not yet fully supported in the API. Hierarchical attributes support both read and write, but when read they are not calculated and instead the raw value is returned:

# The hierarchical attribute `my_attribute` is set on Shot but this will not
# be reflected on the children. Instead the raw value is returned.
print shot['custom_attributes']['my_attribute']
'foo'
print task['custom_attributes']['my_attribute']
None

To work around this limitation it is possible to use the legacy api for hierarchical attributes or to manually query the parents for values and use the first value that is set.

Validation

Custom attributes are validated on the ftrack server before persisted. The validation will check that the type of the data is correct for the custom attribute.

If the value set is not valid a ftrack_api.exception.ServerError is raised with debug information:

shot['custom_attributes']['fstart'] = 'test'

Traceback (most recent call last):
    ...
ftrack_api.exception.ServerError: Server reported error:
ValidationError(Custom attribute value for "fstart" must be of type number.
Got "test" of type <type 'unicode'>)

Managing custom attribute configurations

From the API it is not only possible to read and update custom attributes for entities, but also managing custom attribute configurations.

Existing custom attribute configurations can be queried as

# Print all existing custom attribute configurations.
print session.query('CustomAttributeConfiguration').all()

Use Session.create() to create a new custom attribute configuration:

# Get the custom attribute type.
custom_attribute_type = session.query(
    'CustomAttributeType where name is "text"'
).one()

# Create a custom attribute configuration.
session.create('CustomAttributeConfiguration', {
    'entity_type': 'assetversion',
    'type': custom_attribute_type,
    'label': 'Asset version text attribute',
    'key': 'asset_version_text_attribute',
    'default': 'bar',
    'config': json.dumps({'markdown': False})
})

# Persist it to the ftrack instance.
session.commit()

Tip

The example above does not add security roles. This can be done either from System Settings in the ftrack web application, or by following the Security roles example.

Global or project specific

A custom attribute can be global or project specific depending on the project_id attribute:

# Create a custom attribute configuration.
session.create('CustomAttributeConfiguration', {
    # Set the `project_id` and the custom attribute will only be available
    # on `my_project`.
    'project_id': my_project['id'],
    'entity_type': 'assetversion',
    'type': custom_attribute_type,
    'label': 'Asset version text attribute',
    'key': 'asset_version_text_attribute',
    'default': 'bar',
    'config': json.dumps({'markdown': False})
})
session.commit()

A project specific custom attribute can be changed to a global:

custom_attribute_configuration['project_id'] = None
session.commit()

Changing a global custom attribute configuration to a project specific is not allowed.

Entity types

Custom attribute configuration entity types are using a legacy notation. A configuration can have one of the following as entity_type:

task:

Represents TypedContext (Folder, Shot, Sequence, Task, etc.) custom attribute configurations. When setting this as entity_type the object_type_id must be set as well.

Creating a text custom attribute for Folder:

custom_attribute_type = session.query(
    'CustomAttributeType where name is "text"'
).one()
object_type = session.query('ObjectType where name is "Folder"').one()
session.create('CustomAttributeConfiguration', {
    'entity_type': 'task',
    'object_type_id': object_type['id'],
    'type': custom_attribute_type,
    'label': 'Foo',
    'key': 'foo',
    'default': 'bar',
})
session.commit()

Can be associated with a project_id.

show:

Represents Projects custom attribute configurations.

Can be associated with a project_id.

assetversion:

Represents AssetVersion custom attribute configurations.

Can be associated with a project_id.

user:

Represents User custom attribute configurations.

Must be global and cannot be associated with a project_id.

list:

Represents List custom attribute configurations.

Can be associated with a project_id.

asset:

Represents Asset custom attribute configurations.

Note

Asset custom attributes have limited support in the ftrack web interface.

Can be associated with a project_id.

It is not possible to change type after a custom attribute configuration has been created.

Custom attribute configuration types

Custom attributes can be of different data types depending on what type is set in the configuration. Some types requires an extra json encoded config to be set:

text:

A sting type custom attribute.

The default value must be either str or unicode.

Can be either presented as raw text or markdown formatted in applicaitons which support it. This is configured through a markwdown key:

# Get the custom attribute type.
custom_attribute_type = session.query(
    'CustomAttributeType where name is "text"'
).one()

# Create a custom attribute configuration.
session.create('CustomAttributeConfiguration', {
    'entity_type': 'assetversion',
    'type': custom_attribute_type,
    'label': 'Asset version text attribute',
    'key': 'asset_version_text_attribute',
    'default': 'bar',
    'config': json.dumps({'markdown': False})
})

# Persist it to the ftrack instance.
session.commit()
boolean:

A boolean type custom attribute.

The default value must be a bool.

No config is required.

date:

A date type custom attribute.

The default value must be an arrow date - e.g. arrow.Arrow(2017, 2, 8).

No config is required.

enumerator:

An enumerator type custom attribute.

The default value must be a list with either str or unicode.

The enumerator can either be single or multi select. The config must a json dump of a dictionary containing multiSelect and data. Where multiSelect is True or False and data is a list of options. Each option should be a dictionary containing value and menu, where menu is meant to be used as label in a user interface.

Create a custom attribute enumerator:

custom_attribute_type = session.query(
    'CustomAttributeType where name is "enumerator"'
).first()
session.create('CustomAttributeConfiguration', {
    'entity_type': 'assetversion',
    'type': custom_attribute_type,
    'label': 'Enumerator attribute',
    'key': 'enumerator_attribute',
    'default': ['bar'],
    'config': json.dumps({
        'multiSelect': True,
        'data': json.dumps([
            {'menu': 'Foo', 'value': 'foo'},
            {'menu': 'Bar', 'value': 'bar'}
        ])
    })
})
session.commit()
dynamic enumerator:

An enumerator type where available options are fetched from remote. Created in the same way as enumerator but without data.

number:

A number custom attribute can be either decimal or integer for presentation.

This can be configured through the isdecimal config option:

custom_attribute_type = session.query(
    'CustomAttributeType where name is "number"'
).first()
session.create('CustomAttributeConfiguration', {
    'entity_type': 'assetversion',
    'type': custom_attribute_type,
    'label': 'Number attribute',
    'key': 'number_attribute',
    'default': 42,
    'config': json.dumps({
        'isdecimal': True
    })
})
session.commit()

Changing default

It is possible to update the default value of a custom attribute configuration. This will not change the value of any existing custom attributes:

# Change the default value of custom attributes. This will only affect
# newly created entities.
custom_attribute_configuration['default'] = 43
session.commit()

Security roles

By default new custom attribute configurations and the entity values are not readable or writable by any security role.

This can be configured through the read_security_roles and write_security_roles attributes:

# Pick random security role.
security_role = session.query('SecurityRole').first()
custom_attribute_type = session.query(
    'CustomAttributeType where name is "date"'
).first()
session.create('CustomAttributeConfiguration', {
    'entity_type': 'assetversion',
    'type': custom_attribute_type,
    'label': 'Date attribute',
    'key': 'date_attribute',
    'default': arrow.Arrow(2017, 2, 8),
    'write_security_roles': [security_role],
    'read_security_roles': [security_role]
})
session.commit()

Note

Setting the correct security role is important and must be changed to whatever security role is appropriate for your configuration and intended purpose.

Custom attribute groups

A custom attribute configuration can be categorized using a CustomAttributeGroup:

group = session.query('CustomAttributeGroup').first()
security_role = session.query('SecurityRole').first()
custom_attribute_type = session.query(
    'CustomAttributeType where name is "enumerator"'
).first()
session.create('CustomAttributeConfiguration', {
    'entity_type': 'assetversion',
    'type': custom_attribute_type,
    'label': 'Enumerator attribute',
    'key': 'enumerator_attribute',
    'default': ['bar'],
    'config': json.dumps({
        'multiSelect': True,
        'data': json.dumps([
            {'menu': 'Foo', 'value': 'foo'},
            {'menu': 'Bar', 'value': 'bar'}
        ])
    }),
    'group': group,
    'write_security_roles': [security_role],
    'read_security_roles': [security_role]
})
session.commit()

Using scopes

Entities can be queried based on their scopes:

>>> tasks = session.query(
...     'Task where scopes.name is "London"'
... )

Scopes can be read and modified for entities:

>>> scope = session.query(
...     'Scope where name is "London"'
... )[0]
...
... if scope in task['scopes']:
...     task['scopes'].remove(scope)
... else:
...     task['scopes'].append(scope)

Managing jobs

Jobs can be used to display feedback to users in the ftrack web interface when performing long running tasks in the API.

To create a job use Session.create():

user = # Get a user from ftrack.

job = session.create('Job', {
    'user': user,
    'status': 'running'
})

The created job will appear as running in the jobs menu for the specified user. To set a description on the job, add a dictionary containing description as the data key:

Note

In the current version of the API the dictionary needs to be JSON serialised.

import json

job = session.create('Job', {
    'user': user,
    'status': 'running',
    'data': json.dumps({
        'description': 'My custom job description.'
    })
})

When the long running task has finished simply set the job as completed and continue with the next task.

job['status'] = 'done'
session.commit()

Attachments

Job attachments are files that are attached to a job. In the ftrack web interface these attachments can be downloaded by clicking on a job in the Jobs menu.

To get a job’s attachments through the API you can use the job_components relation and then use the ftrack server location to get the download URL:

server_location = session.query(
    'Location where name is "ftrack.server"'
).one()

for job_component in job['job_components']:
    print 'Download URL: {0}'.format(
        server_location.get_url(job_component['component'])
    )

To add an attachment to a job you have to add it to the ftrack server location and create a jobComponent:

server_location = session.query(
    'Location where name is "ftrack.server"'
).one()

# Create component and name it "My file".
component = session.create_component(
    '/path/to/file',
    data={'name': 'My file'},
    location=server_location
)

# Attach the component to the job.
session.create(
    'JobComponent',
    {'component_id': component['id'], 'job_id': job['id']}
)

session.commit()

Note

The ftrack web interface does only support downloading one attachment so attaching more than one will have limited support in the web interface.

Using notes

Notes can be written on almost all levels in ftrack. To retrieve notes on an entity you can either query them or use the relation called notes:

task = session.query('Task').first()

# Retrieve notes using notes property.
notes_on_task = task['notes']

# Or query them.
notes_on_task = session.query('Note where parent_id is "{}"'.format(
    task['id']
))

Note

It’s currently not possible to use the parent property when querying notes or to use the parent property on notes:

task = session.query('Task').first()

# This won't work in the current version of the API.
session.query('Note where parent.id is "{}"'.format(
    task['id']
))

# Neither will this.
parent_of_note = note['parent']

To create new notes you can either use the helper method called create_note() on any entity that can have notes or use Session.create() to create them manually:

user = session.query('User').first()

# Create note using the helper method.
note = task.create_note('My new note', author=user)

# Manually create a note
note = session.create('Note', {
    'content': 'My new note',
    'author': user
})

task['notes'].append(note)

Replying to an existing note can also be done with a helper method or by using Session.create():

# Create using helper method.
first_note_on_task = task['notes'][0]
first_note_on_task.create_reply('My new reply on note', author=user)

# Create manually
reply = session.create('Note', {
    'content': 'My new note',
    'author': user
})

first_note_on_task.replies.append(reply)

Notes can have labels. Use the label argument to set labels on the note using the helper method:

label = session.query(
    'NoteLabel where name is "External Note"'
).first()

note = task.create_note(
    'New note with external category', author=user, labels=[label]
)

Or add labels to notes when creating a note manually:

label = session.query(
    'NoteLabel where name is "External Note"'
).first()

note = session.create('Note', {
    'content': 'New note with external category',
    'author': user
})

session.create('NoteLabelLink', {
    'note_id': note['id],
    'label_id': label['id']
})

task['notes'].append(note)

Note

Support for labels on notes was added in ftrack server version 4.3. For older versions of the server, NoteCategory can be used instead.

To specify a category when creating a note simply pass a NoteCategory instance to the helper method:

category = session.query(
    'NoteCategory where name is "External Note"'
).first()

note = task.create_note(
    'New note with external category', author=user, category=category
)

When writing notes you might want to direct the note to someone. This is done by adding users as recipients. If a user is added as a recipient the user will receive notifications and the note will be displayed in their inbox.

To add recipients pass a list of user or group instances to the helper method:

john = session.query('User where username is "john"').one()
animation_group = session.query('Group where name is "Animation"').first()

note = task.create_note(
    'Note with recipients', author=user, recipients=[john, animation_group]
)

Attachments

Note attachments are files that are attached to a note. In the ftrack web interface these attachments appears next to the note and can be downloaded by the user.

To get a note’s attachments through the API you can use the note_components relation and then use the ftrack server location to get the download URL:

server_location = session.query(
    'Location where name is "ftrack.server"'
).one()

for note_component in note['note_components']:
    print 'Download URL: {0}'.format(
        server_location.get_url(note_component['component'])
    )

To add an attachment to a note you have to add it to the ftrack server location and create a NoteComponent:

server_location = session.query(
    'Location where name is "ftrack.server"'
).one()

# Create component and name it "My file".
component = session.create_component(
    '/path/to/file',
    data={'name': 'My file'},
    location=server_location
)

# Attach the component to the note.
session.create(
    'NoteComponent',
    {'component_id': component['id'], 'note_id': note['id']}
)

session.commit()

Using lists

Lists can be used to create a collection of asset versions or objects such as tasks. It could be a list of items that should be sent to client, be included in todays review session or items that belong together in way that is different from the project hierarchy.

There are two types of lists, one for asset versions and one for other objects such as tasks.

To create a list use Session.create():

user = # Get a user from ftrack.
project = # Get a project from ftrack.
list_category = # Get a list category from ftrack.

asset_version_list = session.create('AssetVersionList', {
    'owner': user,
    'project': project,
    'category': list_category
})

task_list = session.create('TypedContextList', {
    'owner': user,
    'project': project,
    'category': list_category
})

Then add items to the list like this:

asset_version_list['items'].append(asset_version)
task_list['items'].append(task)

And remove items from the list like this:

asset_version_list['items'].remove(asset_version)
task_list['items'].remove(task)

Using timers

Timers can be used to track how much time has been spend working on something.

To start a timer for a user:

user = # Get a user from ftrack.
task = # Get a task from ftrack.

user.start_timer(task)

A timer has now been created for that user and should show up in the ftrack web UI.

To stop the currently running timer for a user and create a timelog from it:

user = # Get a user from ftrack.

timelog = user.stop_timer()

Note

Starting a timer when a timer is already running will raise in an exception. Use the force parameter to automatically stop the running timer first.

user.start_timer(task, force=True)

Working with assignments and allocations

The API exposes assignments and allocations relationships on objects in the project hierarchy. You can use these to retrieve the allocated or assigned resources, which can be either groups or users.

Allocations can be used to allocate users or groups to a project team, while assignments are more explicit and is used to assign users to tasks. Both assignment and allocations are modelled as Appointment objects, with a type attribute indicating the type of the appoinment.

The following example retrieves all users part of the project team:

# Retrieve a project
project = session.query('Project').first()

# Set to hold all users part of the project team
project_team = set()

# Add all allocated groups and users
for allocation in project['allocations']:

    # Resource may be either a group or a user
    resource = allocation['resource']

    # If the resource is a group, add its members
    if isinstance(resource, session.types['Group']):
        for membership in resource['memberships']:
            user = membership['user']
            project_team.add(user)

    # The resource is a user, add it.
    else:
        user = resource
        project_team.add(user)

The next example shows how to assign the current user to a task:

# Retrieve a task and the current user
task = session.query('Task').first()
current_user = session.query(
    u'User where username is {0}'.format(session.api_user)
).one()

# Create a new Appointment of type assignment.
session.create('Appointment', {
    'context': task,
    'resource': current_user,
    'type': 'assignment'
})

# Finally, persist the new assignment
session.commit()

To list all users assigned to a task, see the following example:

task = session.query('Task').first()
users = session.query(
    'select first_name, last_name from User '
    'where assignments any (context_id = "{0}")'.format(task['id'])
)
for user in users:
    print user['first_name'], user['last_name']

To list the current user’s assigned tasks, see the example below:

assigned_tasks = session.query(
    'select link from Task '
    'where assignments any (resource.username = "{0}")'.format(session.api_user)
)
for task in assigned_tasks:
    print u' / '.join(item['name'] for item in task['link'])

Working with thumbnails

Components can be used as thumbnails on various entities, including Project, Task, AssetVersion and User. To create and set a thumbnail you can use the helper method create_thumbnail() on any entity that can have a thumbnail:

task = session.get('Task', my_task_id)
thumbnail_component = task.create_thumbnail('/path/to/image.jpg')

It is also possible to set an entity thumbnail by setting its thumbnail relation or thumbnail_id attribute to a component you would like to use as a thumbnail. For a component to be usable as a thumbnail, it should

  1. Be a FileComponent.
  2. Exist in the ftrack.server location.
  3. Be of an appropriate resolution and valid file type.

The following example creates a new component in the server location, and uses that as a thumbnail for a task:

task = session.get('Task', my_task_id)
server_location = session.query(
    'Location where name is "ftrack.server"'
).one()

thumbnail_component = session.create_component(
    '/path/to/image.jpg',
    dict(name='thumbnail'),
    location=server_location
)
task['thumbnail'] = thumbnail_component
session.commit()

The next example reuses a version’s thumbnail for the asset parent thumbnail:

asset_version = session.get('AssetVersion', my_asset_version_id)
asset_parent = asset_version['asset']['parent']
asset_parent['thumbnail_id'] = asset_version['thumbnail_id']
session.commit()

Retrieving thumbnail URL

To get an URL to a thumbnail, thumbnail_component, which can be used used to download or display the image in an interface, use the following:

import ftrack_api.symbol
server_location = session.get('Location', ftrack_api.symbol.SERVER_LOCATION_ID)
thumbnail_url = server_location.get_thumbnail_url(thumbnail_component)
thumbnail_url_tiny = server_location.get_thumbnail_url(
    thumbnail_component, size=100
)
thumbnail_url_large = server_location.get_thumbnail_url(
    thumbnail_component, size=500
)

Encoding media

Media such as images and video can be encoded by the ftrack server to allow playing it in the ftrack web interface. Media can be encoded using ftrack_api.session.Session.encode_media() which accepts a path to a file or an existing component in the ftrack.server location.

Here is an example of how to encode a video and read the output:

job = session.encode_media('/PATH/TO/MEDIA')
job_data = json.loads(job['data'])

print 'Source component id', job_data['source_component_id']
print 'Keeping original component', job_data['keep_original']
for output in job_data['output']:
    print u'Output component - id: {0}, format: {1}'.format(
        output['component_id'], output['format']
    )

You can also call the corresponding helper method on an asset version, to have the encoded components automatically associated with the version:

job = asset_version.encode_media('/PATH/TO/MEDIA')

It is also possible to get the URL to an encoded component once the job has finished:

job = session.encode_media('/PATH/TO/MEDIA')

# Wait for job to finish.

location = session.query('Location where name is "ftrack.server"').one()
for component in job['job_components']:
    print location.get_url(component)

Media can also be an existing component in another location. Before encoding it, the component needs to be added to the ftrack.server location:

location = session.query('Location where name is "ftrack.server"').one()
location.add_component(component)
session.commit()

job = session.encode_media(component)

Publishing for web review

Follow the Encoding media example if you want to upload and encode media using ftrack.

If you already have a file encoded in the correct format and want to bypass the built-in encoding in ftrack, you can create the component manually and add it to the ftrack.server location:

# Retrieve or create version.
version = session.query('AssetVersion', 'SOME-ID')

server_location = session.query('Location where name is "ftrack.server"').one()
filepath = '/path/to/local/file.mp4'

component = version.create_component(
    path=filepath,
    data={
        'name': 'ftrackreview-mp4'
    },
    location=server_location
)

# Meta data needs to contain *frameIn*, *frameOut* and *frameRate*.
component['metadata']['ftr_meta'] = json.dumps({
    'frameIn': 0,
    'frameOut': 150,
    'frameRate': 25
})

component.session.commit()

To publish an image for review the steps are similar:

# Retrieve or create version.
version = session.query('AssetVersion', 'SOME-ID')

server_location = session.query('Location where name is "ftrack.server"').one()
filepath = '/path/to/image.jpg'

component = version.create_component(
    path=filepath,
    data={
        'name': 'ftrackreview-image'
    },
    location=server_location
)

# Meta data needs to contain *format*.
component['metadata']['ftr_meta'] = json.dumps({
    'format': 'image'
})

component.session.commit()

Here is a list of components names and how they should be used:

Component name Use
ftrackreview-image Images reviewable in the browser
ftrackreview-mp4 H.264/mp4 video reviewable in browser
ftrackreview-webm WebM video reviewable in browser

Note

Make sure to use the pre-defined component names and set the ftr_meta on the components or review will not work.

Publishing versions

To know more about publishing and the concepts around publishing, read the ftrack article about publishing.

To publish an asset you first need to get the context where the asset should be published:

# Get a task from a given id.
task = session.get('Task', '423ac382-e61d-4802-8914-dce20c92b740')

And the parent of the task which will be used to publish the asset on:

asset_parent = task['parent']

Then we create an asset and a version on the asset:

asset_type = session.query('AssetType where name is "Geometry"').one()
asset = session.create('Asset', {
    'name': 'My asset',
    'type': asset_type,
    'parent': asset_parent
})
asset_version = session.create('AssetVersion', {
    'asset': asset,
    'task': task
})

session.commit()

Note

The task is not used as the parent of the asset, instead the task is linked directly to the AssetVersion.

Then when we have a version where we can create the components:

asset_version.create_component(
    '/path/to/a/file.mov', location='auto'
)
asset_version.create_component(
    '/path/to/a/another-file.mov', location='auto'
)

session.commit()

This will automatically create a new component and add it to the location which has been configured as the first in priority.

Components can also be named and added to a custom location like this:

location = session.query('Location where name is "my-location"')
asset_version.create_component(
    '/path/to/a/file.mov',
    data={
        'name': 'foobar'
    },
    location=location
)

Working with user security roles

The API exposes SecurityRole and UserSecurityRole that can be used to specify who should have access to certain data on different projects.

List all available security roles like this:

security_roles = session.query(
    'select name from SecurityRole where type is "PROJECT"'
)

Note

We only query for project roles since those are the ones we can add to a user for certain projects. Other types include API and ASSIGNED. Type API can only be added to global API keys, which is currently not supported via the api and type ASSIGNED only applies to assigned tasks.

To get all security roles from a user we can either use relations like this:

for user_security_role in user['user_security_roles']:
    if user_security_role['is_all_projects']:
        result_string = 'all projects'
    else:
        result_string = ', '.join(
            [project['full_name'] for project in user_security_role['projects']]
        )

    print 'User has security role "{0}" which is valid on {1}.'.format(
        user_security_role['security_role']['name'],
        result_string
    )

or query them directly like this:

user_security_roles = session.query(
    'UserSecurityRole where user.username is "{0}"'.format(session.api_user)
).all()

User security roles can also be added to a user for all projects like this:

project_manager_role = session.query(
    'SecurityRole where name is "Project Manager"'
).one()

session.create('UserSecurityRole', {
    'is_all_projects': True,
    'user': user,
    'security_role': project_manager_role
})
session.commit()

or for certain projects only like this:

projects = session.query(
    'Project where full_name is "project1" or full_name is "project2"'
).all()[:]

session.create('UserSecurityRole', {
    'user': user,
    'security_role': project_manager_role,
    'projects': projects
})
session.commit()

Working with Task Templates

Task templates can help you organize your workflows by building a collection of tasks to be applied for specific contexts. They can be applied to all Context objects for example Project, Sequences, Shots, etc…

Query task templates

Retrive all task templates and there tasks for a project:

project = session.query('Project').first()

for task_template in project['project_schema']['task_templates']:
    print('\ntask template: {0}'.format(
        task_template['name']
    ))

    for task_type in [t['task_type'] for t in task_template['items']]:
        print('\ttask type: {0}'.format(
            task_type['name']
        ))

“Apply” a task template

Create all tasks in a random task template directly under the project:

project = session.query('Project').first()

task_template = random.choice(
    project['project_schema']['task_templates']
)

for task_type in [t['task_type'] for t in task_template['items']]:
    session.create(
        'Task', {
            'name': task_type['name'],
            'type': task_type,
            'parent': project
        }
    )

session.commit()

Sync users with LDAP

If ftrack is configured to connect to LDAP you may trigger a synchronization through the api using the ftrack_api.session.Session.call():

result = session.call([
    dict(
        action='delayed_job',
        job_type='SYNC_USERS_LDAP'
    )
])
job = result[0]['data]

You will get a ftrack_api.entity.job.Job instance back which can be used to check the success of the job:

if job.get('status') == 'failed':
    # The job failed get the error.
    logging.error(job.get('data'))

Invite user

Here we create a new user and send them a invitation through mail

Create a new user:

user_email = 'artist@mail.vfx-company.com'

new_user = session.create(
    'User', {
        'username':user_email,
        'email':user_email,
        'is_active':True
    }
)

session.commit()

Invite our new user:

new_user.send_invite()

API Reference

ftrack_api

ftrack_api.mixin(instance, mixin_class, name=None)[source]

Mixin mixin_class to instance.

name can be used to specify new class name. If not specified then one will be generated.

ftrack_api.accessor

ftrack_api.accessor.base
class ftrack_api.accessor.base.Accessor[source]

Provide data access to a location.

A location represents a specific storage, but access to that storage may vary. For example, both local filesystem and FTP access may be possible for the same storage. An accessor implements these different ways of accessing the same data location.

As different accessors may access the same location, only part of a data path that is commonly understood may be stored in the database. The format of this path should be a contract between the accessors that require access to the same location and is left as an implementation detail. As such, this system provides no guarantee that two different accessors can provide access to the same location, though this is a clear goal. The path stored centrally is referred to as the resource identifier and should be used when calling any of the accessor methods that accept a resource_identifier argument.

__init__()[source]

Initialise location accessor.

list(resource_identifier)[source]

Return list of entries in resource_identifier container.

Each entry in the returned list should be a valid resource identifier.

Raise AccessorResourceNotFoundError if resource_identifier does not exist or AccessorResourceInvalidError if resource_identifier is not a container.

exists(resource_identifier)[source]

Return if resource_identifier is valid and exists in location.

is_file(resource_identifier)[source]

Return whether resource_identifier refers to a file.

is_container(resource_identifier)[source]

Return whether resource_identifier refers to a container.

is_sequence(resource_identifier)[source]

Return whether resource_identifier refers to a file sequence.

open(resource_identifier, mode='rb')[source]

Return Data for resource_identifier.

remove(resource_identifier)[source]

Remove resource_identifier.

Raise AccessorResourceNotFoundError if resource_identifier does not exist.

make_container(resource_identifier, recursive=True)[source]

Make a container at resource_identifier.

If recursive is True, also make any intermediate containers.

Should silently ignore existing containers and not recreate them.

get_container(resource_identifier)[source]

Return resource_identifier of container for resource_identifier.

Raise AccessorParentResourceNotFoundError if container of resource_identifier could not be determined.

remove_container(resource_identifier)[source]

Remove container at resource_identifier.

get_filesystem_path(resource_identifier)[source]

Return filesystem path for resource_identifier.

Raise AccessorFilesystemPathError if filesystem path could not be determined from resource_identifier or AccessorUnsupportedOperationError if retrieving filesystem paths is not supported by this accessor.

get_url(resource_identifier)[source]

Return URL for resource_identifier.

Raise AccessorFilesystemPathError if URL could not be determined from resource_identifier or AccessorUnsupportedOperationError if retrieving URL is not supported by this accessor.

ftrack_api.accessor.disk
class ftrack_api.accessor.disk.DiskAccessor(prefix, **kw)[source]

Provide disk access to a location.

Expect resource identifiers to refer to relative filesystem paths.

__init__(prefix, **kw)[source]

Initialise location accessor.

prefix specifies the base folder for the disk based structure and will be prepended to any path. It should be specified in the syntax of the current OS.

list(resource_identifier)[source]

Return list of entries in resource_identifier container.

Each entry in the returned list should be a valid resource identifier.

Raise AccessorResourceNotFoundError if resource_identifier does not exist or AccessorResourceInvalidError if resource_identifier is not a container.

exists(resource_identifier)[source]

Return if resource_identifier is valid and exists in location.

is_file(resource_identifier)[source]

Return whether resource_identifier refers to a file.

is_container(resource_identifier)[source]

Return whether resource_identifier refers to a container.

is_sequence(resource_identifier)[source]

Return whether resource_identifier refers to a file sequence.

open(resource_identifier, mode='rb')[source]

Return Data for resource_identifier.

remove(resource_identifier)[source]

Remove resource_identifier.

Raise AccessorResourceNotFoundError if resource_identifier does not exist.

make_container(resource_identifier, recursive=True)[source]

Make a container at resource_identifier.

If recursive is True, also make any intermediate containers.

get_container(resource_identifier)[source]

Return resource_identifier of container for resource_identifier.

Raise AccessorParentResourceNotFoundError if container of resource_identifier could not be determined.

get_filesystem_path(resource_identifier)[source]

Return filesystem path for resource_identifier.

For example:

>>> accessor = DiskAccessor('my.location', '/mountpoint')
>>> print accessor.get_filesystem_path('test.txt')
/mountpoint/test.txt
>>> print accessor.get_filesystem_path('/mountpoint/test.txt')
/mountpoint/test.txt

Raise ftrack_api.exception.AccessorFilesystemPathError if filesystem path could not be determined from resource_identifier.

get_url(resource_identifier)

Return URL for resource_identifier.

Raise AccessorFilesystemPathError if URL could not be determined from resource_identifier or AccessorUnsupportedOperationError if retrieving URL is not supported by this accessor.

remove_container(resource_identifier)

Remove container at resource_identifier.

ftrack_api.accessor.disk.error_handler(**kw)[source]

Conform raised OSError/IOError exception to appropriate FTrack error.

ftrack_api.accessor.server
class ftrack_api.accessor.server.ServerFile(resource_identifier, session, mode='rb')[source]

Representation of a server file.

__init__(resource_identifier, session, mode='rb')[source]

Initialise file.

flush()[source]

Flush all changes.

read(limit=None)[source]

Read file.

close()

Flush buffers and prevent further access.

seek(offset, whence=0)

Move internal pointer by offset.

tell()

Return current position of internal pointer.

write(content)

Write content at current position.

ftrack_api.entity

ftrack_api.entity.asset_version
class ftrack_api.entity.asset_version.AssetVersion(session, data=None, reconstructing=False)[source]

Represent asset version.

create_component(path, data=None, location=None)[source]

Create a new component from path with additional data

Note

This is a helper method. To create components manually use the standard Session.create() method.

path can be a string representing a filesystem path to the data to use for the component. The path can also be specified as a sequence string, in which case a sequence component with child components for each item in the sequence will be created automatically. The accepted format for a sequence is ‘{head}{padding}{tail} [{ranges}]’. For example:

'/path/to/file.%04d.ext [1-5, 7, 8, 10-20]'

data should be a dictionary of any additional data to construct the component with (as passed to Session.create()). This version is automatically set as the component’s version.

If location is specified then automatically add component to that location.

encode_media(media, keep_original='auto')[source]

Return a new Job that encode media to make it playable in browsers.

media can be a path to a file or a FileComponent in the ftrack.server location.

The job will encode media based on the file type and job data contains information about encoding in the following format:

{
    'output': [{
        'format': 'video/mp4',
        'component_id': 'e2dc0524-b576-11d3-9612-080027331d74'
    }, {
        'format': 'image/jpeg',
        'component_id': '07b82a97-8cf9-11e3-9383-20c9d081909b'
    }],
    'source_component_id': 'e3791a09-7e11-4792-a398-3d9d4eefc294',
    'keep_original': True
}

The output components are associated with the job via the job_components relation.

An image component will always be generated if possible, and will be set as the version’s thumbnail.

The new components will automatically be associated with the version. A server version of 3.3.32 or higher is required for this to function properly.

If media is a file path, a new source component will be created and added to the ftrack server location and a call to commit() will be issued. If media is a FileComponent, it will be assumed to be in available in the ftrack.server location.

If keep_original is not set, the original media will be kept if it is a FileComponent, and deleted if it is a file path. You can specify True or False to change this behavior.

__init__(session, data=None, reconstructing=False)

Initialise entity.

session is an instance of ftrack_api.session.Session that this entity instance is bound to.

data is a mapping of key, value pairs to apply as initial attribute values.

reconstructing indicates whether this entity is being reconstructed, such as from a query, and therefore should not have any special creation logic applied, such as initialising defaults for missing data.

attributes = None
clear()

Reset all locally modified attribute values.

default_projections = None
entity_type = 'Entity'
get(k[, d]) → D[k] if k in D, else d. d defaults to None.
items()

Return list of tuples of (key, value) pairs.

Note

Will fetch all values from the server if not already fetched or set locally.

keys() → a set-like object providing a view on D's keys
merge(entity, merged=None)

Merge entity attribute values and other data into this entity.

Only merge values from entity that are not ftrack_api.symbol.NOT_SET.

Return a list of changes made with each change being a mapping with the keys:

  • type - Either ‘remote_attribute’, ‘local_attribute’ or ‘property’.
  • name - The name of the attribute / property modified.
  • old_value - The previous value.
  • new_value - The new merged value.
pop(k[, d]) → v, remove specified key and return the corresponding value.

If key is not found, d is returned if given, otherwise KeyError is raised.

popitem() → (k, v), remove and return some (key, value) pair

as a 2-tuple; but raise KeyError if D is empty.

primary_key_attributes = None
setdefault(k[, d]) → D.get(k,d), also set D[k]=d if k not in D
update([E, ]**F) → None. Update D from mapping/iterable E and F.

If E present and has a .keys() method, does: for k in E: D[k] = E[k] If E present and lacks .keys() method, does: for (k, v) in E: D[k] = v In either case, this is followed by: for k, v in F.items(): D[k] = v

values()

Return list of values.

ftrack_api.entity.base
class ftrack_api.entity.base.DynamicEntityTypeMetaclass[source]

Custom metaclass to customise representation of dynamic classes.

Note

Derive from same metaclass as derived bases to avoid conflicts.

__init__

Initialize self. See help(type(self)) for accurate signature.

mro()

Return a type’s method resolution order.

register(subclass)

Register a virtual subclass of an ABC.

Returns the subclass, to allow usage as a class decorator.

class ftrack_api.entity.base.Entity(session, data=None, reconstructing=False)[source]

Base class for all entities.

entity_type = 'Entity'
attributes = None
primary_key_attributes = None
default_projections = None
__init__(session, data=None, reconstructing=False)[source]

Initialise entity.

session is an instance of ftrack_api.session.Session that this entity instance is bound to.

data is a mapping of key, value pairs to apply as initial attribute values.

reconstructing indicates whether this entity is being reconstructed, such as from a query, and therefore should not have any special creation logic applied, such as initialising defaults for missing data.

values()[source]

Return list of values.

items()[source]

Return list of tuples of (key, value) pairs.

Note

Will fetch all values from the server if not already fetched or set locally.

clear()[source]

Reset all locally modified attribute values.

merge(entity, merged=None)[source]

Merge entity attribute values and other data into this entity.

Only merge values from entity that are not ftrack_api.symbol.NOT_SET.

Return a list of changes made with each change being a mapping with the keys:

  • type - Either ‘remote_attribute’, ‘local_attribute’ or ‘property’.
  • name - The name of the attribute / property modified.
  • old_value - The previous value.
  • new_value - The new merged value.
get(k[, d]) → D[k] if k in D, else d. d defaults to None.
keys() → a set-like object providing a view on D's keys
pop(k[, d]) → v, remove specified key and return the corresponding value.

If key is not found, d is returned if given, otherwise KeyError is raised.

popitem() → (k, v), remove and return some (key, value) pair

as a 2-tuple; but raise KeyError if D is empty.

setdefault(k[, d]) → D.get(k,d), also set D[k]=d if k not in D
update([E, ]**F) → None. Update D from mapping/iterable E and F.

If E present and has a .keys() method, does: for k in E: D[k] = E[k] If E present and lacks .keys() method, does: for (k, v) in E: D[k] = v In either case, this is followed by: for k, v in F.items(): D[k] = v

ftrack_api.entity.component
class ftrack_api.entity.component.Component(session, data=None, reconstructing=False)[source]

Represent a component.

get_availability(locations=None)[source]

Return availability in locations.

If locations is None, all known locations will be checked.

Return a dictionary of {location_id:percentage_availability}

__init__(session, data=None, reconstructing=False)

Initialise entity.

session is an instance of ftrack_api.session.Session that this entity instance is bound to.

data is a mapping of key, value pairs to apply as initial attribute values.

reconstructing indicates whether this entity is being reconstructed, such as from a query, and therefore should not have any special creation logic applied, such as initialising defaults for missing data.

attributes = None
clear()

Reset all locally modified attribute values.

default_projections = None
entity_type = 'Entity'
get(k[, d]) → D[k] if k in D, else d. d defaults to None.
items()

Return list of tuples of (key, value) pairs.

Note

Will fetch all values from the server if not already fetched or set locally.

keys() → a set-like object providing a view on D's keys
merge(entity, merged=None)

Merge entity attribute values and other data into this entity.

Only merge values from entity that are not ftrack_api.symbol.NOT_SET.

Return a list of changes made with each change being a mapping with the keys:

  • type - Either ‘remote_attribute’, ‘local_attribute’ or ‘property’.
  • name - The name of the attribute / property modified.
  • old_value - The previous value.
  • new_value - The new merged value.
pop(k[, d]) → v, remove specified key and return the corresponding value.

If key is not found, d is returned if given, otherwise KeyError is raised.

popitem() → (k, v), remove and return some (key, value) pair

as a 2-tuple; but raise KeyError if D is empty.

primary_key_attributes = None
setdefault(k[, d]) → D.get(k,d), also set D[k]=d if k not in D
update([E, ]**F) → None. Update D from mapping/iterable E and F.

If E present and has a .keys() method, does: for k in E: D[k] = E[k] If E present and lacks .keys() method, does: for (k, v) in E: D[k] = v In either case, this is followed by: for k, v in F.items(): D[k] = v

values()

Return list of values.

class ftrack_api.entity.component.CreateThumbnailMixin[source]

Mixin to add create_thumbnail method on entity class.

create_thumbnail(path, data=None)[source]

Set entity thumbnail from path.

Creates a thumbnail component using in the ftrack.server location Session.create_component The thumbnail component will be created using data if specified. If no component name is given, thumbnail will be used.

The file is expected to be of an appropriate size and valid file type.

Note

A Session.commit will be automatically issued.

__init__

Initialize self. See help(type(self)) for accurate signature.

ftrack_api.entity.factory
class ftrack_api.entity.factory.Factory[source]

Entity class factory.

__init__()[source]

Initialise factory.

create(schema, bases=None)[source]

Create and return entity class from schema.

bases should be a list of bases to give the constructed class. If not specified, default to ftrack_api.entity.base.Entity.

create_scalar_attribute(class_name, name, mutable, computed, default, data_type)[source]

Return appropriate scalar attribute instance.

create_reference_attribute(class_name, name, mutable, reference)[source]

Return appropriate reference attribute instance.

create_collection_attribute(class_name, name, mutable)[source]

Return appropriate collection attribute instance.

create_mapped_collection_attribute(class_name, name, mutable, reference)[source]

Return appropriate mapped collection attribute instance.

class ftrack_api.entity.factory.PerSessionDefaultKeyMaker[source]

Generate key for defaults.

__init__()

Initialise key maker.

key(*items)

Return key for items.

ftrack_api.entity.factory.memoise_session(function)

Memoiser for use with callables that should be called once per session.

class ftrack_api.entity.factory.StandardFactory[source]

Standard entity class factory.

create(schema, bases=None)[source]

Create and return entity class from schema.

create_mapped_collection_attribute(class_name, name, mutable, reference)[source]

Return appropriate mapped collection attribute instance.

__init__()

Initialise factory.

create_collection_attribute(class_name, name, mutable)

Return appropriate collection attribute instance.

create_reference_attribute(class_name, name, mutable, reference)

Return appropriate reference attribute instance.

create_scalar_attribute(class_name, name, mutable, computed, default, data_type)

Return appropriate scalar attribute instance.

ftrack_api.entity.job
class ftrack_api.entity.job.Job(session, data=None, reconstructing=False)[source]

Represent job.

__init__(session, data=None, reconstructing=False)[source]

Initialise entity.

session is an instance of ftrack_api.session.Session that this entity instance is bound to.

data is a mapping of key, value pairs to apply as initial attribute values.

To set a job description visible in the web interface, data can contain a key called data which should be a JSON serialised dictionary containing description:

data = {
    'status': 'running',
    'data': json.dumps(dict(description='My job description.')),
    ...
}

Will raise a ValueError if data contains type and type is set to something not equal to “api_job”.

reconstructing indicates whether this entity is being reconstructed, such as from a query, and therefore should not have any special creation logic applied, such as initialising defaults for missing data.

attributes = None
clear()

Reset all locally modified attribute values.

default_projections = None
entity_type = 'Entity'
get(k[, d]) → D[k] if k in D, else d. d defaults to None.
items()

Return list of tuples of (key, value) pairs.

Note

Will fetch all values from the server if not already fetched or set locally.

keys() → a set-like object providing a view on D's keys
merge(entity, merged=None)

Merge entity attribute values and other data into this entity.

Only merge values from entity that are not ftrack_api.symbol.NOT_SET.

Return a list of changes made with each change being a mapping with the keys:

  • type - Either ‘remote_attribute’, ‘local_attribute’ or ‘property’.
  • name - The name of the attribute / property modified.
  • old_value - The previous value.
  • new_value - The new merged value.
pop(k[, d]) → v, remove specified key and return the corresponding value.

If key is not found, d is returned if given, otherwise KeyError is raised.

popitem() → (k, v), remove and return some (key, value) pair

as a 2-tuple; but raise KeyError if D is empty.

primary_key_attributes = None
setdefault(k[, d]) → D.get(k,d), also set D[k]=d if k not in D
update([E, ]**F) → None. Update D from mapping/iterable E and F.

If E present and has a .keys() method, does: for k in E: D[k] = E[k] If E present and lacks .keys() method, does: for (k, v) in E: D[k] = v In either case, this is followed by: for k, v in F.items(): D[k] = v

values()

Return list of values.

ftrack_api.entity.location
class ftrack_api.entity.location.Location(session, data=None, reconstructing=False)[source]

Represent storage for components.

__init__(session, data=None, reconstructing=False)[source]

Initialise entity.

session is an instance of ftrack_api.session.Session that this entity instance is bound to.

data is a mapping of key, value pairs to apply as initial attribute values.

reconstructing indicates whether this entity is being reconstructed, such as from a query, and therefore should not have any special creation logic applied, such as initialising defaults for missing data.

add_component(component, source, recursive=True)[source]

Add component to location.

component should be a single component instance.

source should be an instance of another location that acts as the source.

Raise ftrack_api.ComponentInLocationError if the component already exists in this location.

Raise ftrack_api.LocationError if managing data and the generated target structure for the component already exists according to the accessor. This helps prevent potential data loss by avoiding overwriting existing data. Note that there is a race condition between the check and the write so if another process creates data at the same target during that period it will be overwritten.

Note

A Session.commit may be automatically issued as part of the component registration.

add_components(components, sources, recursive=True, _depth=0)[source]

Add components to location.

components should be a list of component instances.

sources may be either a single source or a list of sources. If a list then each corresponding index in sources will be used for each component. A source should be an instance of another location.

Raise ftrack_api.exception.ComponentInLocationError if any component in components already exists in this location. In this case, no changes will be made and no data transferred.

Raise ftrack_api.exception.LocationError if managing data and the generated target structure for the component already exists according to the accessor. This helps prevent potential data loss by avoiding overwriting existing data. Note that there is a race condition between the check and the write so if another process creates data at the same target during that period it will be overwritten.

Note

A Session.commit may be automatically issued as part of the components registration.

Important

If this location manages data then the components data is first transferred to the target prescribed by the structure plugin, using the configured accessor. If any component fails to transfer then ftrack_api.exception.LocationError is raised and none of the components are registered with the database. In this case it is left up to the caller to decide and act on manually cleaning up any transferred data using the ‘transferred’ detail in the raised error.

Likewise, after transfer, all components are registered with the database in a batch call. If any component causes an error then all components will remain unregistered and ftrack_api.exception.LocationError will be raised detailing issues and any transferred data under the ‘transferred’ detail key.

remove_component(component, recursive=True)[source]

Remove component from location.

Note

A Session.commit may be automatically issued as part of the component deregistration.

remove_components(components, recursive=True)[source]

Remove components from location.

Note

A Session.commit may be automatically issued as part of the components deregistration.

get_component_availability(component)[source]

Return availability of component in this location as a float.

get_component_availabilities(components)[source]

Return availabilities of components in this location.

Return list of float values corresponding to each component.

get_resource_identifier(component)[source]

Return resource identifier for component.

Raise ftrack_api.exception.ComponentNotInLocationError if the component is not present in this location.

get_resource_identifiers(components)[source]

Return resource identifiers for components.

Raise ftrack_api.exception.ComponentNotInLocationError if any of the components are not present in this location.

get_filesystem_path(component)[source]

Return filesystem path for component.

get_filesystem_paths(components)[source]

Return filesystem paths for components.

get_url(component)[source]

Return url for component.

Raise AccessorFilesystemPathError if URL could not be determined from component or AccessorUnsupportedOperationError if retrieving URL is not supported by the location’s accessor.

attributes = None
clear()

Reset all locally modified attribute values.

default_projections = None
entity_type = 'Entity'
get(k[, d]) → D[k] if k in D, else d. d defaults to None.
items()

Return list of tuples of (key, value) pairs.

Note

Will fetch all values from the server if not already fetched or set locally.

keys() → a set-like object providing a view on D's keys
merge(entity, merged=None)

Merge entity attribute values and other data into this entity.

Only merge values from entity that are not ftrack_api.symbol.NOT_SET.

Return a list of changes made with each change being a mapping with the keys:

  • type - Either ‘remote_attribute’, ‘local_attribute’ or ‘property’.
  • name - The name of the attribute / property modified.
  • old_value - The previous value.
  • new_value - The new merged value.
pop(k[, d]) → v, remove specified key and return the corresponding value.

If key is not found, d is returned if given, otherwise KeyError is raised.

popitem() → (k, v), remove and return some (key, value) pair

as a 2-tuple; but raise KeyError if D is empty.

primary_key_attributes = None
setdefault(k[, d]) → D.get(k,d), also set D[k]=d if k not in D
update([E, ]**F) → None. Update D from mapping/iterable E and F.

If E present and has a .keys() method, does: for k in E: D[k] = E[k] If E present and lacks .keys() method, does: for (k, v) in E: D[k] = v In either case, this is followed by: for k, v in F.items(): D[k] = v

values()

Return list of values.

class ftrack_api.entity.location.MemoryLocationMixin[source]

Represent storage for components.

Unlike a standard location, only store metadata for components in this location in memory rather than persisting to the database.

__init__

Initialize self. See help(type(self)) for accurate signature.

clear() → None. Remove all items from D.
get(k[, d]) → D[k] if k in D, else d. d defaults to None.
items() → a set-like object providing a view on D's items
keys() → a set-like object providing a view on D's keys
pop(k[, d]) → v, remove specified key and return the corresponding value.

If key is not found, d is returned if given, otherwise KeyError is raised.

popitem() → (k, v), remove and return some (key, value) pair

as a 2-tuple; but raise KeyError if D is empty.

setdefault(k[, d]) → D.get(k,d), also set D[k]=d if k not in D
update([E, ]**F) → None. Update D from mapping/iterable E and F.

If E present and has a .keys() method, does: for k in E: D[k] = E[k] If E present and lacks .keys() method, does: for (k, v) in E: D[k] = v In either case, this is followed by: for k, v in F.items(): D[k] = v

values() → an object providing a view on D's values
class ftrack_api.entity.location.UnmanagedLocationMixin[source]

Location that does not manage data.

__init__

Initialize self. See help(type(self)) for accurate signature.

clear() → None. Remove all items from D.
get(k[, d]) → D[k] if k in D, else d. d defaults to None.
items() → a set-like object providing a view on D's items
keys() → a set-like object providing a view on D's keys
pop(k[, d]) → v, remove specified key and return the corresponding value.

If key is not found, d is returned if given, otherwise KeyError is raised.

popitem() → (k, v), remove and return some (key, value) pair

as a 2-tuple; but raise KeyError if D is empty.

setdefault(k[, d]) → D.get(k,d), also set D[k]=d if k not in D
update([E, ]**F) → None. Update D from mapping/iterable E and F.

If E present and has a .keys() method, does: for k in E: D[k] = E[k] If E present and lacks .keys() method, does: for (k, v) in E: D[k] = v In either case, this is followed by: for k, v in F.items(): D[k] = v

values() → an object providing a view on D's values
class ftrack_api.entity.location.OriginLocationMixin[source]

Special origin location that expects sources as filepaths.

__init__

Initialize self. See help(type(self)) for accurate signature.

clear() → None. Remove all items from D.
get(k[, d]) → D[k] if k in D, else d. d defaults to None.
items() → a set-like object providing a view on D's items
keys() → a set-like object providing a view on D's keys
pop(k[, d]) → v, remove specified key and return the corresponding value.

If key is not found, d is returned if given, otherwise KeyError is raised.

popitem() → (k, v), remove and return some (key, value) pair

as a 2-tuple; but raise KeyError if D is empty.

setdefault(k[, d]) → D.get(k,d), also set D[k]=d if k not in D
update([E, ]**F) → None. Update D from mapping/iterable E and F.

If E present and has a .keys() method, does: for k in E: D[k] = E[k] If E present and lacks .keys() method, does: for (k, v) in E: D[k] = v In either case, this is followed by: for k, v in F.items(): D[k] = v

values() → an object providing a view on D's values
class ftrack_api.entity.location.ServerLocationMixin[source]

Location representing ftrack server.

Adds convenience methods to location, specific to ftrack server.

get_thumbnail_url(component, size=None)[source]

Return thumbnail url for component.

Optionally, specify size to constrain the downscaled image to size x size pixels.

Raise AccessorFilesystemPathError if URL could not be determined from resource_identifier or AccessorUnsupportedOperationError if retrieving URL is not supported by the location’s accessor.

__init__

Initialize self. See help(type(self)) for accurate signature.

clear() → None. Remove all items from D.
get(k[, d]) → D[k] if k in D, else d. d defaults to None.
items() → a set-like object providing a view on D's items
keys() → a set-like object providing a view on D's keys
pop(k[, d]) → v, remove specified key and return the corresponding value.

If key is not found, d is returned if given, otherwise KeyError is raised.

popitem() → (k, v), remove and return some (key, value) pair

as a 2-tuple; but raise KeyError if D is empty.

setdefault(k[, d]) → D.get(k,d), also set D[k]=d if k not in D
update([E, ]**F) → None. Update D from mapping/iterable E and F.

If E present and has a .keys() method, does: for k in E: D[k] = E[k] If E present and lacks .keys() method, does: for (k, v) in E: D[k] = v In either case, this is followed by: for k, v in F.items(): D[k] = v

values() → an object providing a view on D's values
ftrack_api.entity.note
class ftrack_api.entity.note.Note(session, data=None, reconstructing=False)[source]

Represent a note.

create_reply(content, author)[source]

Create a reply with content and author.

Note

This is a helper method. To create replies manually use the standard Session.create() method.

__init__(session, data=None, reconstructing=False)

Initialise entity.

session is an instance of ftrack_api.session.Session that this entity instance is bound to.

data is a mapping of key, value pairs to apply as initial attribute values.

reconstructing indicates whether this entity is being reconstructed, such as from a query, and therefore should not have any special creation logic applied, such as initialising defaults for missing data.

attributes = None
clear()

Reset all locally modified attribute values.

default_projections = None
entity_type = 'Entity'
get(k[, d]) → D[k] if k in D, else d. d defaults to None.
items()

Return list of tuples of (key, value) pairs.

Note

Will fetch all values from the server if not already fetched or set locally.

keys() → a set-like object providing a view on D's keys
merge(entity, merged=None)

Merge entity attribute values and other data into this entity.

Only merge values from entity that are not ftrack_api.symbol.NOT_SET.

Return a list of changes made with each change being a mapping with the keys:

  • type - Either ‘remote_attribute’, ‘local_attribute’ or ‘property’.
  • name - The name of the attribute / property modified.
  • old_value - The previous value.
  • new_value - The new merged value.
pop(k[, d]) → v, remove specified key and return the corresponding value.

If key is not found, d is returned if given, otherwise KeyError is raised.

popitem() → (k, v), remove and return some (key, value) pair

as a 2-tuple; but raise KeyError if D is empty.

primary_key_attributes = None
setdefault(k[, d]) → D.get(k,d), also set D[k]=d if k not in D
update([E, ]**F) → None. Update D from mapping/iterable E and F.

If E present and has a .keys() method, does: for k in E: D[k] = E[k] If E present and lacks .keys() method, does: for (k, v) in E: D[k] = v In either case, this is followed by: for k, v in F.items(): D[k] = v

values()

Return list of values.

class ftrack_api.entity.note.CreateNoteMixin[source]

Mixin to add create_note method on entity class.

create_note(content, author, recipients=None, category=None, labels=None)[source]

Create note with content, author.

NoteLabels can be set by including labels.

Note category can be set by including category.

recipients can be specified as a list of user or group instances.

__init__

Initialize self. See help(type(self)) for accurate signature.

ftrack_api.entity.project_schema
class ftrack_api.entity.project_schema.ProjectSchema(session, data=None, reconstructing=False)[source]

Class representing ProjectSchema.

get_statuses(schema, type_id=None)[source]

Return statuses for schema and optional type_id.

type_id is the id of the Type for a TypedContext and can be used to get statuses where the workflow has been overridden.

get_types(schema)[source]

Return types for schema.

__init__(session, data=None, reconstructing=False)

Initialise entity.

session is an instance of ftrack_api.session.Session that this entity instance is bound to.

data is a mapping of key, value pairs to apply as initial attribute values.

reconstructing indicates whether this entity is being reconstructed, such as from a query, and therefore should not have any special creation logic applied, such as initialising defaults for missing data.

attributes = None
clear()

Reset all locally modified attribute values.

default_projections = None
entity_type = 'Entity'
get(k[, d]) → D[k] if k in D, else d. d defaults to None.
items()

Return list of tuples of (key, value) pairs.

Note

Will fetch all values from the server if not already fetched or set locally.

keys() → a set-like object providing a view on D's keys
merge(entity, merged=None)

Merge entity attribute values and other data into this entity.

Only merge values from entity that are not ftrack_api.symbol.NOT_SET.

Return a list of changes made with each change being a mapping with the keys:

  • type - Either ‘remote_attribute’, ‘local_attribute’ or ‘property’.
  • name - The name of the attribute / property modified.
  • old_value - The previous value.
  • new_value - The new merged value.
pop(k[, d]) → v, remove specified key and return the corresponding value.

If key is not found, d is returned if given, otherwise KeyError is raised.

popitem() → (k, v), remove and return some (key, value) pair

as a 2-tuple; but raise KeyError if D is empty.

primary_key_attributes = None
setdefault(k[, d]) → D.get(k,d), also set D[k]=d if k not in D
update([E, ]**F) → None. Update D from mapping/iterable E and F.

If E present and has a .keys() method, does: for k in E: D[k] = E[k] If E present and lacks .keys() method, does: for (k, v) in E: D[k] = v In either case, this is followed by: for k, v in F.items(): D[k] = v

values()

Return list of values.

ftrack_api.entity.user
class ftrack_api.entity.user.User(session, data=None, reconstructing=False)[source]

Represent a user.

start_timer(context=None, comment='', name=None, force=False)[source]

Start a timer for context and return it.

force can be used to automatically stop an existing timer and create a timelog for it. If you need to get access to the created timelog, use stop_timer() instead.

comment and name are optional but will be set on the timer.

Note

This method will automatically commit the changes and if force is False then it will fail with a ftrack_api.exception.NotUniqueError exception if a timer is already running.

stop_timer()[source]

Stop the current timer and return a timelog created from it.

If a timer is not running, a ftrack_api.exception.NoResultFoundError exception will be raised.

Note

This method will automatically commit the changes.

send_invite()[source]

Send a invation email to the user

reset_api_key()[source]

Reset the users api key.

__init__(session, data=None, reconstructing=False)

Initialise entity.

session is an instance of ftrack_api.session.Session that this entity instance is bound to.

data is a mapping of key, value pairs to apply as initial attribute values.

reconstructing indicates whether this entity is being reconstructed, such as from a query, and therefore should not have any special creation logic applied, such as initialising defaults for missing data.

attributes = None
clear()

Reset all locally modified attribute values.

default_projections = None
entity_type = 'Entity'
get(k[, d]) → D[k] if k in D, else d. d defaults to None.
items()

Return list of tuples of (key, value) pairs.

Note

Will fetch all values from the server if not already fetched or set locally.

keys() → a set-like object providing a view on D's keys
merge(entity, merged=None)

Merge entity attribute values and other data into this entity.

Only merge values from entity that are not ftrack_api.symbol.NOT_SET.

Return a list of changes made with each change being a mapping with the keys:

  • type - Either ‘remote_attribute’, ‘local_attribute’ or ‘property’.
  • name - The name of the attribute / property modified.
  • old_value - The previous value.
  • new_value - The new merged value.
pop(k[, d]) → v, remove specified key and return the corresponding value.

If key is not found, d is returned if given, otherwise KeyError is raised.

popitem() → (k, v), remove and return some (key, value) pair

as a 2-tuple; but raise KeyError if D is empty.

primary_key_attributes = None
setdefault(k[, d]) → D.get(k,d), also set D[k]=d if k not in D
update([E, ]**F) → None. Update D from mapping/iterable E and F.

If E present and has a .keys() method, does: for k in E: D[k] = E[k] If E present and lacks .keys() method, does: for (k, v) in E: D[k] = v In either case, this is followed by: for k, v in F.items(): D[k] = v

values()

Return list of values.

ftrack_api.event

ftrack_api.event.base
class ftrack_api.event.base.Event(topic, id=None, data=None, sent=None, source=None, target='', in_reply_to_event=None)[source]

Represent a single event.

__init__(topic, id=None, data=None, sent=None, source=None, target='', in_reply_to_event=None)[source]

Initialise event.

topic is the required topic for the event. It can use a dotted notation to demarcate groupings. For example, ‘ftrack.update’.

id is the unique id for this event instance. It is primarily used when replying to an event. If not supplied a default uuid based value will be used.

data refers to event specific data. It should be a mapping structure and defaults to an empty dictionary if not supplied.

sent is the timestamp the event is sent. It will be set automatically as send time unless specified here.

source is information about where the event originated. It should be a mapping and include at least a unique id value under an ‘id’ key. If not specified, senders usually populate the value automatically at publish time.

target can be an expression that targets this event. For example, a reply event would target the event to the sender of the source event. The expression will be tested against subscriber information only.

in_reply_to_event is used when replying to an event and should contain the unique id of the event being replied to.

stop()[source]

Stop further processing of this event.

is_stopped()[source]

Return whether event has been stopped.

clear() → None. Remove all items from D.
get(k[, d]) → D[k] if k in D, else d. d defaults to None.
items() → a set-like object providing a view on D's items
keys() → a set-like object providing a view on D's keys
pop(k[, d]) → v, remove specified key and return the corresponding value.

If key is not found, d is returned if given, otherwise KeyError is raised.

popitem() → (k, v), remove and return some (key, value) pair

as a 2-tuple; but raise KeyError if D is empty.

setdefault(k[, d]) → D.get(k,d), also set D[k]=d if k not in D
update([E, ]**F) → None. Update D from mapping/iterable E and F.

If E present and has a .keys() method, does: for k in E: D[k] = E[k] If E present and lacks .keys() method, does: for (k, v) in E: D[k] = v In either case, this is followed by: for k, v in F.items(): D[k] = v

values() → an object providing a view on D's values
ftrack_api.event.expression
class ftrack_api.event.expression.Parser[source]

Parse string based expression into Expression instance.

__init__()[source]

Initialise parser.

parse(expression)[source]

Parse string expression into Expression.

Raise ftrack_api.exception.ParseError if expression could not be parsed.

class ftrack_api.event.expression.Expression[source]

Represent a structured expression to test candidates against.

match(candidate)[source]

Return whether candidate satisfies this expression.

__init__

Initialize self. See help(type(self)) for accurate signature.

class ftrack_api.event.expression.All(expressions=None)[source]

Match candidate that matches all of the specified expressions.

Note

If no expressions are supplied then will always match.

__init__(expressions=None)[source]

Initialise with list of expressions to match against.

match(candidate)[source]

Return whether candidate satisfies this expression.

class ftrack_api.event.expression.Any(expressions=None)[source]

Match candidate that matches any of the specified expressions.

Note

If no expressions are supplied then will never match.

__init__(expressions=None)[source]

Initialise with list of expressions to match against.

match(candidate)[source]

Return whether candidate satisfies this expression.

class ftrack_api.event.expression.Not(expression)[source]

Negate expression.

__init__(expression)[source]

Initialise with expression to negate.

match(candidate)[source]

Return whether candidate satisfies this expression.

class ftrack_api.event.expression.Condition(key, operator, value)[source]

Represent condition.

__init__(key, operator, value)[source]

Initialise condition.

key is the key to check on the data when matching. It can be a nested key represented by dots. For example, ‘data.eventType’ would attempt to match candidate[‘data’][‘eventType’]. If the candidate is missing any of the requested keys then the match fails immediately.

operator is the operator function to use to perform the match between the retrieved candidate value and the conditional value.

If value is a string, it can use a wildcard ‘*’ at the end to denote that any values matching the substring portion are valid when matching equality only.

match(candidate)[source]

Return whether candidate satisfies this expression.

ftrack_api.event.hub
class ftrack_api.event.hub.SocketIoSession(id, heartbeatTimeout, supportedTransports)
__init__

Initialize self. See help(type(self)) for accurate signature.

count()

Return number of occurrences of value.

heartbeatTimeout

Alias for field number 1

id

Alias for field number 0

index()

Return first index of value.

Raises ValueError if the value is not present.

supportedTransports

Alias for field number 2

class ftrack_api.event.hub.ServerDetails(scheme, hostname, port)
__init__

Initialize self. See help(type(self)) for accurate signature.

count()

Return number of occurrences of value.

hostname

Alias for field number 1

index()

Return first index of value.

Raises ValueError if the value is not present.

port

Alias for field number 2

scheme

Alias for field number 0

class ftrack_api.event.hub.EventHub(server_url, api_user, api_key)[source]

Manage routing of events.

__init__(server_url, api_user, api_key)[source]

Initialise hub, connecting to ftrack server_url.

api_user is the user to authenticate as and api_key is the API key to authenticate with.

get_server_url()[source]

Return URL to server.

get_network_location()[source]

Return network location part of url (hostname with optional port).

secure

Return whether secure connection used.

connect()[source]

Initialise connection to server.

Raise ftrack_api.exception.EventHubConnectionError if already connected or connection fails.

connected

Return if connected.

disconnect(unsubscribe=True)[source]

Disconnect from server.

Raise ftrack_api.exception.EventHubConnectionError if not currently connected.

If unsubscribe is True then unsubscribe all current subscribers automatically before disconnecting.

reconnect(attempts=10, delay=5)[source]

Reconnect to server.

Make attempts number of attempts with delay in seconds between each attempt.

Note

All current subscribers will be automatically resubscribed after successful reconnection.

Raise ftrack_api.exception.EventHubConnectionError if fail to reconnect.

wait(duration=None)[source]

Wait for events and handle as they arrive.

If duration is specified, then only process events until duration is reached. duration is in seconds though float values can be used for smaller values.

get_subscriber_by_identifier(identifier)[source]

Return subscriber with matching identifier.

Return None if no subscriber with identifier found.

subscribe(subscription, callback, subscriber=None, priority=100)[source]

Register callback for subscription.

A subscription is a string that can specify in detail which events the callback should receive. The filtering is applied against each event object. Nested references are supported using ‘.’ separators. For example, ‘topic=foo and data.eventType=Shot’ would match the following event:

<Event {'topic': 'foo', 'data': {'eventType': 'Shot'}}>

The callback should accept an instance of ftrack_api.event.base.Event as its sole argument.

Callbacks are called in order of priority. The lower the priority number the sooner it will be called, with 0 being the first. The default priority is 100. Note that priority only applies against other callbacks registered with this hub and not as a global priority.

An earlier callback can prevent processing of subsequent callbacks by calling Event.stop() on the passed event before returning.

Warning

Handlers block processing of other received events. For long running callbacks it is advisable to delegate the main work to another process or thread.

A callback can be attached to subscriber information that details the subscriber context. A subscriber context will be generated automatically if not supplied.

Note

The subscription will be stored locally, but until the server receives notification of the subscription it is possible the callback will not be called.

Return subscriber identifier.

Raise ftrack_api.exception.NotUniqueError if a subscriber with the same identifier already exists.

unsubscribe(subscriber_identifier)[source]

Unsubscribe subscriber with subscriber_identifier.

Note

If the server is not reachable then it won’t be notified of the unsubscription. However, the subscriber will be removed locally regardless.

publish(event, synchronous=False, on_reply=None, on_error='raise')[source]

Publish event.

If synchronous is specified as True then this method will wait and return a list of results from any called callbacks.

Note

Currently, if synchronous is True then only locally registered callbacks will be called and no event will be sent to the server. This may change in future.

on_reply is an optional callable to call with any reply event that is received in response to the published event.

Note

Will not be called when synchronous is True.

If on_error is set to ‘ignore’ then errors raised during publish of event will be caught by this method and ignored.

publish_reply(source_event, data, source=None)[source]

Publish a reply event to source_event with supplied data.

If source is specified it will be used for the source value of the sent event.

subscription(subscription, callback, subscriber=None, priority=100)[source]

Return context manager with callback subscribed to subscription.

The subscribed callback will be automatically unsubscribed on exit of the context manager.

ftrack_api.event.subscriber
class ftrack_api.event.subscriber.Subscriber(subscription, callback, metadata, priority)[source]

Represent event subscriber.

__init__(subscription, callback, metadata, priority)[source]

Initialise subscriber.

interested_in(event)[source]

Return whether subscriber interested in event.

ftrack_api.event.subscription
class ftrack_api.event.subscription.Subscription(subscription)[source]

Represent a subscription.

parser = <ftrack_api.event.expression.Parser object>
__init__(subscription)[source]

Initialise with subscription.

includes(event)[source]

Return whether subscription includes event.

ftrack_api.resource_identifier_transformer

ftrack_api.resource_identifier_transformer.base
class ftrack_api.resource_identifier_transformer.base.ResourceIdentifierTransformer(session)[source]

Transform resource identifiers.

Provide ability to modify resource identifier before it is stored centrally (encode()), or after it has been retrieved, but before it is used locally (decode()).

For example, you might want to decompose paths into a set of key, value pairs to store centrally and then compose a path from those values when reading back.

Note

This is separate from any transformations an ftrack_api.accessor.base.Accessor may perform and is targeted towards common transformations.

__init__(session)[source]

Initialise resource identifier transformer.

session should be the ftrack_api.session.Session instance to use for communication with the server.

encode(resource_identifier, context=None)[source]

Return encoded resource_identifier for storing centrally.

A mapping of context values may be supplied to guide the transformation.

decode(resource_identifier, context=None)[source]

Return decoded resource_identifier for use locally.

A mapping of context values may be supplied to guide the transformation.

ftrack_api.structure

ftrack_api.structure.base
class ftrack_api.structure.base.Structure(prefix='')[source]

Structure plugin interface.

A structure plugin should compute appropriate paths for data.

__init__(prefix='')[source]

Initialise structure.

get_resource_identifier(entity, context=None)[source]

Return a resource identifier for supplied entity.

context can be a mapping that supplies additional information.

ftrack_api.structure.id
class ftrack_api.structure.id.IdStructure(prefix='')[source]

Id based structure supporting Components only.

A components unique id will be used to form a path to store the data at. To avoid millions of entries in one directory each id is chunked into four prefix directories with the remainder used to name the file:

/prefix/1/2/3/4/56789

If the component has a defined filetype it will be added to the path:

/prefix/1/2/3/4/56789.exr

Components that are children of container components will be placed inside the id structure of their parent:

/prefix/1/2/3/4/56789/355827648d.exr
/prefix/1/2/3/4/56789/ajf24215b5.exr

However, sequence children will be named using their label as an index and a common prefix of ‘file.’:

/prefix/1/2/3/4/56789/file.0001.exr
/prefix/1/2/3/4/56789/file.0002.exr
get_resource_identifier(entity, context=None)[source]

Return a resource identifier for supplied entity.

context can be a mapping that supplies additional information.

__init__(prefix='')

Initialise structure.

ftrack_api.structure.origin
class ftrack_api.structure.origin.OriginStructure(prefix='')[source]

Origin structure that passes through existing resource identifier.

get_resource_identifier(entity, context=None)[source]

Return a resource identifier for supplied entity.

context should be a mapping that includes at least a ‘source_resource_identifier’ key that refers to the resource identifier to pass through.

__init__(prefix='')

Initialise structure.

ftrack_api.structure.standard
class ftrack_api.structure.standard.StandardStructure(project_versions_prefix=None, illegal_character_substitute='_')[source]

Project hierarchy based structure that only supports Components.

The resource identifier is generated from the project code, the name of objects in the project structure, asset name and version number:

my_project/folder_a/folder_b/asset_name/v003

If the component is a FileComponent then the name of the component and the file type are used as filename in the resource_identifier:

my_project/folder_a/folder_b/asset_name/v003/foo.jpg

If the component is a SequenceComponent then a sequence expression, %04d, is used. E.g. a component with the name foo yields:

my_project/folder_a/folder_b/asset_name/v003/foo.%04d.jpg

For the member components their index in the sequence is used:

my_project/folder_a/folder_b/asset_name/v003/foo.0042.jpg

The name of the component is added to the resource identifier if the component is a ContainerComponent. E.g. a container component with the name bar yields:

my_project/folder_a/folder_b/asset_name/v003/bar

For a member of that container the file name is based on the component name and file type:

my_project/folder_a/folder_b/asset_name/v003/bar/baz.pdf
__init__(project_versions_prefix=None, illegal_character_substitute='_')[source]

Initialise structure.

If project_versions_prefix is defined, insert after the project code for versions published directly under the project:

my_project/<project_versions_prefix>/v001/foo.jpg

Replace illegal characters with illegal_character_substitute if defined.

Note

Nested component containers/sequences are not supported.

sanitise_for_filesystem(value)[source]

Return value with illegal filesystem characters replaced.

An illegal character is one that is not typically valid for filesystem usage, such as non ascii characters, or can be awkward to use in a filesystem, such as spaces. Replace these characters with the character specified by illegal_character_substitute on initialisation. If no character was specified as substitute then return value unmodified.

get_resource_identifier(entity, context=None)[source]

Return a resource identifier for supplied entity.

context can be a mapping that supplies additional information, but is unused in this implementation.

Raise a ftrack_api.exeption.StructureError if entity is not attached to a committed version and a committed asset with a parent context.

ftrack_api.attribute

ftrack_api.attribute.merge_references(function)[source]

Decorator to handle merging of references / collections.

class ftrack_api.attribute.Attributes(attributes=None)[source]

Collection of properties accessible by name.

__init__(attributes=None)[source]

Initialize self. See help(type(self)) for accurate signature.

add(attribute)[source]

Add attribute.

remove(attribute)[source]

Remove attribute.

get(name)[source]

Return attribute by name.

If no attribute matches name then return None.

keys()[source]

Return list of attribute names.

class ftrack_api.attribute.Attribute(name, default_value=Symbol(NOT_SET), mutable=True, computed=False)[source]

A name and value pair persisted remotely.

__init__(name, default_value=Symbol(NOT_SET), mutable=True, computed=False)[source]

Initialise attribute with name.

default_value represents the default value for the attribute. It may be a callable. It is not used within the attribute when providing values, but instead exists for other parts of the system to reference.

If mutable is set to False then the local value of the attribute on an entity can only be set when both the existing local and remote values are ftrack_api.symbol.NOT_SET. The exception to this is when the target value is also ftrack_api.symbol.NOT_SET.

If computed is set to True the value is a remote side computed value and should not be long-term cached.

get_entity_storage(entity)[source]

Return attribute storage on entity creating if missing.

name

Return name.

mutable

Return whether attribute is mutable.

computed

Return whether attribute is computed.

get_value(entity)[source]

Return current value for entity.

If a value was set locally then return it, otherwise return last known remote value. If no remote value yet retrieved, make a request for it via the session and block until available.

get_local_value(entity)[source]

Return locally set value for entity.

get_remote_value(entity)[source]

Return remote value for entity.

Note

Only return locally stored remote value, do not fetch from remote.

set_local_value(entity, value)[source]

Set local value for entity.

set_remote_value(entity, value)[source]

Set remote value.

Note

Only set locally stored remote value, do not persist to remote.

populate_remote_value(entity)[source]

Populate remote value for entity.

is_modified(entity)[source]

Return whether local value set and differs from remote.

Note

Will not fetch remote value so may report True even when values are the same on the remote.

is_set(entity)[source]

Return whether a value is set for entity.

class ftrack_api.attribute.ScalarAttribute(name, data_type, **kw)[source]

Represent a scalar value.

__init__(name, data_type, **kw)[source]

Initialise property.

computed

Return whether attribute is computed.

get_entity_storage(entity)

Return attribute storage on entity creating if missing.

get_local_value(entity)

Return locally set value for entity.

get_remote_value(entity)

Return remote value for entity.

Note

Only return locally stored remote value, do not fetch from remote.

get_value(entity)

Return current value for entity.

If a value was set locally then return it, otherwise return last known remote value. If no remote value yet retrieved, make a request for it via the session and block until available.

is_modified(entity)

Return whether local value set and differs from remote.

Note

Will not fetch remote value so may report True even when values are the same on the remote.

is_set(entity)

Return whether a value is set for entity.

mutable

Return whether attribute is mutable.

name

Return name.

populate_remote_value(entity)

Populate remote value for entity.

set_local_value(entity, value)

Set local value for entity.

set_remote_value(entity, value)

Set remote value.

Note

Only set locally stored remote value, do not persist to remote.

class ftrack_api.attribute.ReferenceAttribute(name, entity_type, **kw)[source]

Reference another entity.

__init__(name, entity_type, **kw)[source]

Initialise property.

populate_remote_value(entity)[source]

Populate remote value for entity.

As attribute references another entity, use that entity’s configured default projections to auto populate useful attributes when loading.

is_modified(entity)[source]

Return whether a local value has been set and differs from remote.

Note

Will not fetch remote value so may report True even when values are the same on the remote.

get_value(entity)[source]

Return current value for entity.

If a value was set locally then return it, otherwise return last known remote value. If no remote value yet retrieved, make a request for it via the session and block until available.

computed

Return whether attribute is computed.

get_entity_storage(entity)

Return attribute storage on entity creating if missing.

get_local_value(entity)

Return locally set value for entity.

get_remote_value(entity)

Return remote value for entity.

Note

Only return locally stored remote value, do not fetch from remote.

is_set(entity)

Return whether a value is set for entity.

mutable

Return whether attribute is mutable.

name

Return name.

set_local_value(entity, value)

Set local value for entity.

set_remote_value(entity, value)

Set remote value.

Note

Only set locally stored remote value, do not persist to remote.

class ftrack_api.attribute.AbstractCollectionAttribute(name, default_value=Symbol(NOT_SET), mutable=True, computed=False)[source]

Base class for collection attributes.

collection_class = None

Collection class used by attribute.

get_value(entity)[source]

Return current value for entity.

If a value was set locally then return it, otherwise return last known remote value. If no remote value yet retrieved, make a request for it via the session and block until available.

Note

As value is a collection that is mutable, will transfer a remote value into the local value on access if no local value currently set.

set_local_value(entity, value)[source]

Set local value for entity.

set_remote_value(entity, value)[source]

Set remote value.

Note

Only set locally stored remote value, do not persist to remote.

__init__(name, default_value=Symbol(NOT_SET), mutable=True, computed=False)

Initialise attribute with name.

default_value represents the default value for the attribute. It may be a callable. It is not used within the attribute when providing values, but instead exists for other parts of the system to reference.

If mutable is set to False then the local value of the attribute on an entity can only be set when both the existing local and remote values are ftrack_api.symbol.NOT_SET. The exception to this is when the target value is also ftrack_api.symbol.NOT_SET.

If computed is set to True the value is a remote side computed value and should not be long-term cached.

computed

Return whether attribute is computed.

get_entity_storage(entity)

Return attribute storage on entity creating if missing.

get_local_value(entity)

Return locally set value for entity.

get_remote_value(entity)

Return remote value for entity.

Note

Only return locally stored remote value, do not fetch from remote.

is_modified(entity)

Return whether local value set and differs from remote.

Note

Will not fetch remote value so may report True even when values are the same on the remote.

is_set(entity)

Return whether a value is set for entity.

mutable

Return whether attribute is mutable.

name

Return name.

populate_remote_value(entity)

Populate remote value for entity.

class ftrack_api.attribute.CollectionAttribute(name, default_value=Symbol(NOT_SET), mutable=True, computed=False)[source]

Represent a collection of other entities.

collection_class

alias of ftrack_api.collection.Collection

__init__(name, default_value=Symbol(NOT_SET), mutable=True, computed=False)

Initialise attribute with name.

default_value represents the default value for the attribute. It may be a callable. It is not used within the attribute when providing values, but instead exists for other parts of the system to reference.

If mutable is set to False then the local value of the attribute on an entity can only be set when both the existing local and remote values are ftrack_api.symbol.NOT_SET. The exception to this is when the target value is also ftrack_api.symbol.NOT_SET.

If computed is set to True the value is a remote side computed value and should not be long-term cached.

computed

Return whether attribute is computed.

get_entity_storage(entity)

Return attribute storage on entity creating if missing.

get_local_value(entity)

Return locally set value for entity.

get_remote_value(entity)

Return remote value for entity.

Note

Only return locally stored remote value, do not fetch from remote.

get_value(entity)

Return current value for entity.

If a value was set locally then return it, otherwise return last known remote value. If no remote value yet retrieved, make a request for it via the session and block until available.

Note

As value is a collection that is mutable, will transfer a remote value into the local value on access if no local value currently set.

is_modified(entity)

Return whether local value set and differs from remote.

Note

Will not fetch remote value so may report True even when values are the same on the remote.

is_set(entity)

Return whether a value is set for entity.

mutable

Return whether attribute is mutable.

name

Return name.

populate_remote_value(entity)

Populate remote value for entity.

set_local_value(entity, value)

Set local value for entity.

set_remote_value(entity, value)

Set remote value.

Note

Only set locally stored remote value, do not persist to remote.

class ftrack_api.attribute.KeyValueMappedCollectionAttribute(name, creator, key_attribute, value_attribute, **kw)[source]

Represent a mapped key, value collection of entities.

collection_class

alias of ftrack_api.collection.KeyValueMappedCollectionProxy

__init__(name, creator, key_attribute, value_attribute, **kw)[source]

Initialise attribute with name.

creator should be a function that accepts a dictionary of data and is used by the referenced collection to create new entities in the collection.

key_attribute should be the name of the attribute on an entity in the collection that represents the value for ‘key’ of the dictionary.

value_attribute should be the name of the attribute on an entity in the collection that represents the value for ‘value’ of the dictionary.

computed

Return whether attribute is computed.

get_entity_storage(entity)

Return attribute storage on entity creating if missing.

get_local_value(entity)

Return locally set value for entity.

get_remote_value(entity)

Return remote value for entity.

Note

Only return locally stored remote value, do not fetch from remote.

get_value(entity)

Return current value for entity.

If a value was set locally then return it, otherwise return last known remote value. If no remote value yet retrieved, make a request for it via the session and block until available.

Note

As value is a collection that is mutable, will transfer a remote value into the local value on access if no local value currently set.

is_modified(entity)

Return whether local value set and differs from remote.

Note

Will not fetch remote value so may report True even when values are the same on the remote.

is_set(entity)

Return whether a value is set for entity.

mutable

Return whether attribute is mutable.

name

Return name.

populate_remote_value(entity)

Populate remote value for entity.

set_local_value(entity, value)

Set local value for entity.

set_remote_value(entity, value)

Set remote value.

Note

Only set locally stored remote value, do not persist to remote.

class ftrack_api.attribute.CustomAttributeCollectionAttribute(name, default_value=Symbol(NOT_SET), mutable=True, computed=False)[source]

Represent a mapped custom attribute collection of entities.

collection_class

alias of ftrack_api.collection.CustomAttributeCollectionProxy

__init__(name, default_value=Symbol(NOT_SET), mutable=True, computed=False)

Initialise attribute with name.

default_value represents the default value for the attribute. It may be a callable. It is not used within the attribute when providing values, but instead exists for other parts of the system to reference.

If mutable is set to False then the local value of the attribute on an entity can only be set when both the existing local and remote values are ftrack_api.symbol.NOT_SET. The exception to this is when the target value is also ftrack_api.symbol.NOT_SET.

If computed is set to True the value is a remote side computed value and should not be long-term cached.

computed

Return whether attribute is computed.

get_entity_storage(entity)

Return attribute storage on entity creating if missing.

get_local_value(entity)

Return locally set value for entity.

get_remote_value(entity)

Return remote value for entity.

Note

Only return locally stored remote value, do not fetch from remote.

get_value(entity)

Return current value for entity.

If a value was set locally then return it, otherwise return last known remote value. If no remote value yet retrieved, make a request for it via the session and block until available.

Note

As value is a collection that is mutable, will transfer a remote value into the local value on access if no local value currently set.

is_modified(entity)

Return whether local value set and differs from remote.

Note

Will not fetch remote value so may report True even when values are the same on the remote.

is_set(entity)

Return whether a value is set for entity.

mutable

Return whether attribute is mutable.

name

Return name.

populate_remote_value(entity)

Populate remote value for entity.

set_local_value(entity, value)

Set local value for entity.

set_remote_value(entity, value)

Set remote value.

Note

Only set locally stored remote value, do not persist to remote.

ftrack_api.cache

Caching framework.

Defines a standardised Cache interface for storing data against specific keys. Key generation is also standardised using a KeyMaker interface.

Combining a Cache and KeyMaker allows for memoisation of function calls with respect to the arguments used by using a Memoiser.

As a convenience a simple memoise() decorator is included for quick memoisation of function using a global cache and standard key maker.

class ftrack_api.cache.Cache[source]

Cache interface.

Derive from this to define concrete cache implementations. A cache is centered around the concept of key:value pairings where the key is unique across the cache.

get(key)[source]

Return value for key.

Raise KeyError if key not found.

set(key, value)[source]

Set value for key.

remove(key)[source]

Remove key and return stored value.

Raise KeyError if key not found.

keys()[source]

Return list of keys at this current time.

Warning

Actual keys may differ from those returned due to timing of access.

values()[source]

Return values for current keys.

clear(pattern=None)[source]

Remove all keys matching pattern.

pattern should be a regular expression string.

If pattern is None then all keys will be removed.

__init__

Initialize self. See help(type(self)) for accurate signature.

class ftrack_api.cache.ProxyCache(proxied)[source]

Proxy another cache.

__init__(proxied)[source]

Initialise cache with proxied cache instance.

get(key)[source]

Return value for key.

Raise KeyError if key not found.

set(key, value)[source]

Set value for key.

remove(key)[source]

Remove key and return stored value.

Raise KeyError if key not found.

keys()[source]

Return list of keys at this current time.

Warning

Actual keys may differ from those returned due to timing of access.

clear(pattern=None)

Remove all keys matching pattern.

pattern should be a regular expression string.

If pattern is None then all keys will be removed.

values()

Return values for current keys.

class ftrack_api.cache.LayeredCache(caches)[source]

Layered cache.

__init__(caches)[source]

Initialise cache with caches.

get(key)[source]

Return value for key.

Raise KeyError if key not found.

Attempt to retrieve from cache layers in turn, starting with shallowest. If value retrieved, then also set the value in each higher level cache up from where retrieved.

set(key, value)[source]

Set value for key.

remove(key)[source]

Remove key.

Raise KeyError if key not found in any layer.

keys()[source]

Return list of keys at this current time.

Warning

Actual keys may differ from those returned due to timing of access.

clear(pattern=None)

Remove all keys matching pattern.

pattern should be a regular expression string.

If pattern is None then all keys will be removed.

values()

Return values for current keys.

class ftrack_api.cache.MemoryCache[source]

Memory based cache.

__init__()[source]

Initialise cache.

get(key)[source]

Return value for key.

Raise KeyError if key not found.

set(key, value)[source]

Set value for key.

remove(key)[source]

Remove key.

Raise KeyError if key not found.

keys()[source]

Return list of keys at this current time.

Warning

Actual keys may differ from those returned due to timing of access.

clear(pattern=None)

Remove all keys matching pattern.

pattern should be a regular expression string.

If pattern is None then all keys will be removed.

values()

Return values for current keys.

class ftrack_api.cache.FileCache(path)[source]

File based cache that uses anydbm module.

Note

No locking of the underlying file is performed.

__init__(path)[source]

Initialise cache at path.

get(key)[source]

Return value for key.

Raise KeyError if key not found.

set(key, value)[source]

Set value for key.

remove(key)[source]

Remove key.

Raise KeyError if key not found.

keys()[source]

Return list of keys at this current time.

Warning

Actual keys may differ from those returned due to timing of access.

clear(pattern=None)

Remove all keys matching pattern.

pattern should be a regular expression string.

If pattern is None then all keys will be removed.

values()

Return values for current keys.

class ftrack_api.cache.SerialisedCache(proxied, encode=None, decode=None)[source]

Proxied cache that stores values as serialised data.

__init__(proxied, encode=None, decode=None)[source]

Initialise cache with encode and decode callables.

proxied is the underlying cache to use for storage.

get(key)[source]

Return value for key.

Raise KeyError if key not found.

set(key, value)[source]

Set value for key.

clear(pattern=None)

Remove all keys matching pattern.

pattern should be a regular expression string.

If pattern is None then all keys will be removed.

keys()

Return list of keys at this current time.

Warning

Actual keys may differ from those returned due to timing of access.

remove(key)

Remove key and return stored value.

Raise KeyError if key not found.

values()

Return values for current keys.

class ftrack_api.cache.KeyMaker[source]

Generate unique keys.

__init__()[source]

Initialise key maker.

key(*items)[source]

Return key for items.

class ftrack_api.cache.StringKeyMaker[source]

Generate string key.

__init__()

Initialise key maker.

key(*items)

Return key for items.

class ftrack_api.cache.ObjectKeyMaker[source]

Generate unique keys for objects.

__init__()[source]

Initialise key maker.

key(*items)

Return key for items.

class ftrack_api.cache.Memoiser(cache=None, key_maker=None, return_copies=True)[source]

Memoise function calls using a KeyMaker and Cache.

Example:

>>> memoiser = Memoiser(MemoryCache(), ObjectKeyMaker())
>>> def add(x, y):
...     "Return sum of *x* and *y*."
...     print 'Called'
...     return x + y
...
>>> memoiser.call(add, (1, 2), {})
Called
>>> memoiser.call(add, (1, 2), {})
>>> memoiser.call(add, (1, 3), {})
Called
__init__(cache=None, key_maker=None, return_copies=True)[source]

Initialise with cache and key_maker to use.

If cache is not specified a default MemoryCache will be used. Similarly, if key_maker is not specified a default ObjectKeyMaker will be used.

If return_copies is True then all results returned from the cache will be deep copies to avoid indirect mutation of cached values.

call(function, args=None, kw=None)[source]

Call function with args and kw and return result.

If function was previously called with exactly the same arguments then return cached result if available.

Store result for call in cache.

ftrack_api.cache.memoise_decorator(memoiser)[source]

Decorator to memoise function calls using memoiser.

ftrack_api.cache.memoiser = <ftrack_api.cache.Memoiser object>

Default memoiser.

ftrack_api.cache.memoise(function)

Default memoise decorator using standard cache and key maker.

ftrack_api.collection

class ftrack_api.collection.Collection(entity, attribute, mutable=True, data=None)[source]

A collection of entities.

__init__(entity, attribute, mutable=True, data=None)[source]

Initialise collection.

insert(index, item)[source]

Insert item at index.

append(value)

S.append(value) – append value to the end of the sequence

clear() → None -- remove all items from S
count(value) → integer -- return number of occurrences of value
extend(values)

S.extend(iterable) – extend sequence by appending elements from the iterable

index(value[, start[, stop]]) → integer -- return first index of value.

Raises ValueError if the value is not present.

Supporting start and stop arguments is optional, but recommended.

pop([index]) → item -- remove and return item at index (default last).

Raise IndexError if list is empty or index is out of range.

remove(value)

S.remove(value) – remove first occurrence of value. Raise ValueError if the value is not present.

reverse()

S.reverse() – reverse IN PLACE

class ftrack_api.collection.MappedCollectionProxy(collection)[source]

Common base class for mapped collection of entities.

__init__(collection)[source]

Initialise proxy for collection.

mutable

Return whether collection is mutable.

attribute

Return attribute bound to.

clear() → None. Remove all items from D.
get(k[, d]) → D[k] if k in D, else d. d defaults to None.
items() → a set-like object providing a view on D's items
keys() → a set-like object providing a view on D's keys
pop(k[, d]) → v, remove specified key and return the corresponding value.

If key is not found, d is returned if given, otherwise KeyError is raised.

popitem() → (k, v), remove and return some (key, value) pair

as a 2-tuple; but raise KeyError if D is empty.

setdefault(k[, d]) → D.get(k,d), also set D[k]=d if k not in D
update([E, ]**F) → None. Update D from mapping/iterable E and F.

If E present and has a .keys() method, does: for k in E: D[k] = E[k] If E present and lacks .keys() method, does: for (k, v) in E: D[k] = v In either case, this is followed by: for k, v in F.items(): D[k] = v

values() → an object providing a view on D's values
class ftrack_api.collection.KeyValueMappedCollectionProxy(collection, creator, key_attribute, value_attribute)[source]

A mapped collection of key, value entities.

Proxy a standard Collection as a mapping where certain attributes from the entities in the collection are mapped to key, value pairs.

For example:

>>> collection = [Metadata(key='foo', value='bar'), ...]
>>> mapped = KeyValueMappedCollectionProxy(
...     collection, create_metadata,
...     key_attribute='key', value_attribute='value'
... )
>>> print mapped['foo']
'bar'
>>> mapped['bam'] = 'biz'
>>> print mapped.collection[-1]
Metadata(key='bam', value='biz')
__init__(collection, creator, key_attribute, value_attribute)[source]

Initialise collection.

keys() → a set-like object providing a view on D's keys[source]
attribute

Return attribute bound to.

clear() → None. Remove all items from D.
get(k[, d]) → D[k] if k in D, else d. d defaults to None.
items() → a set-like object providing a view on D's items
mutable

Return whether collection is mutable.

pop(k[, d]) → v, remove specified key and return the corresponding value.

If key is not found, d is returned if given, otherwise KeyError is raised.

popitem() → (k, v), remove and return some (key, value) pair

as a 2-tuple; but raise KeyError if D is empty.

setdefault(k[, d]) → D.get(k,d), also set D[k]=d if k not in D
update([E, ]**F) → None. Update D from mapping/iterable E and F.

If E present and has a .keys() method, does: for k in E: D[k] = E[k] If E present and lacks .keys() method, does: for (k, v) in E: D[k] = v In either case, this is followed by: for k, v in F.items(): D[k] = v

values() → an object providing a view on D's values
class ftrack_api.collection.PerSessionDefaultKeyMaker[source]

Generate key for session.

__init__()

Initialise key maker.

key(*items)

Return key for items.

ftrack_api.collection.memoise_session(function)

Memoiser for use with callables that should be called once per session.

class ftrack_api.collection.CustomAttributeCollectionProxy(collection)[source]

A mapped collection of custom attribute value entities.

__init__(collection)[source]

Initialise collection.

get_configuration_id_from_key(key)[source]

Return id of configuration with matching key.

Raise KeyError if no configuration with matching key found.

attribute

Return attribute bound to.

clear() → None. Remove all items from D.
get(k[, d]) → D[k] if k in D, else d. d defaults to None.
items() → a set-like object providing a view on D's items
keys() → a set-like object providing a view on D's keys
mutable

Return whether collection is mutable.

pop(k[, d]) → v, remove specified key and return the corresponding value.

If key is not found, d is returned if given, otherwise KeyError is raised.

popitem() → (k, v), remove and return some (key, value) pair

as a 2-tuple; but raise KeyError if D is empty.

setdefault(k[, d]) → D.get(k,d), also set D[k]=d if k not in D
update([E, ]**F) → None. Update D from mapping/iterable E and F.

If E present and has a .keys() method, does: for k in E: D[k] = E[k] If E present and lacks .keys() method, does: for (k, v) in E: D[k] = v In either case, this is followed by: for k, v in F.items(): D[k] = v

values() → an object providing a view on D's values

ftrack_api.exception

exception ftrack_api.exception.Error(message=None, details=None)[source]

ftrack specific error.

default_message = 'Unspecified error occurred.'
__init__(message=None, details=None)[source]

Initialise exception with message.

If message is None, the class ‘default_message’ will be used.

details should be a mapping of extra information that can be used in the message and also to provide more context.

args
with_traceback()

Exception.with_traceback(tb) – set self.__traceback__ to tb and return self.

exception ftrack_api.exception.AuthenticationError(message=None, details=None)[source]

Raise when an authentication error occurs.

default_message = 'Authentication error.'
__init__(message=None, details=None)

Initialise exception with message.

If message is None, the class ‘default_message’ will be used.

details should be a mapping of extra information that can be used in the message and also to provide more context.

args
with_traceback()

Exception.with_traceback(tb) – set self.__traceback__ to tb and return self.

exception ftrack_api.exception.ServerError(message=None, details=None)[source]

Raise when the server reports an error.

default_message = 'Server reported error processing request.'
__init__(message=None, details=None)

Initialise exception with message.

If message is None, the class ‘default_message’ will be used.

details should be a mapping of extra information that can be used in the message and also to provide more context.

args
with_traceback()

Exception.with_traceback(tb) – set self.__traceback__ to tb and return self.

exception ftrack_api.exception.ServerCompatibilityError(message=None, details=None)[source]

Raise when server appears incompatible.

default_message = 'Server incompatible.'
__init__(message=None, details=None)

Initialise exception with message.

If message is None, the class ‘default_message’ will be used.

details should be a mapping of extra information that can be used in the message and also to provide more context.

args
with_traceback()

Exception.with_traceback(tb) – set self.__traceback__ to tb and return self.

exception ftrack_api.exception.NotFoundError(message=None, details=None)[source]

Raise when something that should exist is not found.

default_message = 'Not found.'
__init__(message=None, details=None)

Initialise exception with message.

If message is None, the class ‘default_message’ will be used.

details should be a mapping of extra information that can be used in the message and also to provide more context.

args
with_traceback()

Exception.with_traceback(tb) – set self.__traceback__ to tb and return self.

exception ftrack_api.exception.NotUniqueError(message=None, details=None)[source]

Raise when unique value required and duplicate detected.

default_message = 'Non-unique value detected.'
__init__(message=None, details=None)

Initialise exception with message.

If message is None, the class ‘default_message’ will be used.

details should be a mapping of extra information that can be used in the message and also to provide more context.

args
with_traceback()

Exception.with_traceback(tb) – set self.__traceback__ to tb and return self.

exception ftrack_api.exception.IncorrectResultError(message=None, details=None)[source]

Raise when a result is incorrect.

default_message = 'Incorrect result detected.'
__init__(message=None, details=None)

Initialise exception with message.

If message is None, the class ‘default_message’ will be used.

details should be a mapping of extra information that can be used in the message and also to provide more context.

args
with_traceback()

Exception.with_traceback(tb) – set self.__traceback__ to tb and return self.

exception ftrack_api.exception.NoResultFoundError(message=None, details=None)[source]

Raise when a result was expected but no result was found.

default_message = 'Expected result, but no result was found.'
__init__(message=None, details=None)

Initialise exception with message.

If message is None, the class ‘default_message’ will be used.

details should be a mapping of extra information that can be used in the message and also to provide more context.

args
with_traceback()

Exception.with_traceback(tb) – set self.__traceback__ to tb and return self.

exception ftrack_api.exception.MultipleResultsFoundError(message=None, details=None)[source]

Raise when a single result expected, but multiple results found.

default_message = 'Expected single result, but received multiple results.'
__init__(message=None, details=None)

Initialise exception with message.

If message is None, the class ‘default_message’ will be used.

details should be a mapping of extra information that can be used in the message and also to provide more context.

args
with_traceback()

Exception.with_traceback(tb) – set self.__traceback__ to tb and return self.

exception ftrack_api.exception.EntityTypeError(message=None, details=None)[source]

Raise when an entity type error occurs.

default_message = 'Entity type error.'
__init__(message=None, details=None)

Initialise exception with message.

If message is None, the class ‘default_message’ will be used.

details should be a mapping of extra information that can be used in the message and also to provide more context.

args
with_traceback()

Exception.with_traceback(tb) – set self.__traceback__ to tb and return self.

exception ftrack_api.exception.UnrecognisedEntityTypeError(entity_type, **kw)[source]

Raise when an unrecognised entity type detected.

default_message = 'Entity type "{entity_type}" not recognised.'
__init__(entity_type, **kw)[source]

Initialise with entity_type that is unrecognised.

args
with_traceback()

Exception.with_traceback(tb) – set self.__traceback__ to tb and return self.

exception ftrack_api.exception.OperationError(message=None, details=None)[source]

Raise when an operation error occurs.

default_message = 'Operation error.'
__init__(message=None, details=None)

Initialise exception with message.

If message is None, the class ‘default_message’ will be used.

details should be a mapping of extra information that can be used in the message and also to provide more context.

args
with_traceback()

Exception.with_traceback(tb) – set self.__traceback__ to tb and return self.

exception ftrack_api.exception.InvalidStateError(message=None, details=None)[source]

Raise when an invalid state detected.

default_message = 'Invalid state.'
__init__(message=None, details=None)

Initialise exception with message.

If message is None, the class ‘default_message’ will be used.

details should be a mapping of extra information that can be used in the message and also to provide more context.

args
with_traceback()

Exception.with_traceback(tb) – set self.__traceback__ to tb and return self.

exception ftrack_api.exception.InvalidStateTransitionError(current_state, target_state, entity, **kw)[source]

Raise when an invalid state transition detected.

default_message = 'Invalid transition from {current_state!r} to {target_state!r} state for entity {entity!r}'
__init__(current_state, target_state, entity, **kw)[source]

Initialise error.

args
with_traceback()

Exception.with_traceback(tb) – set self.__traceback__ to tb and return self.

exception ftrack_api.exception.AttributeError(message=None, details=None)[source]

Raise when an error related to an attribute occurs.

default_message = 'Attribute error.'
__init__(message=None, details=None)

Initialise exception with message.

If message is None, the class ‘default_message’ will be used.

details should be a mapping of extra information that can be used in the message and also to provide more context.

args
with_traceback()

Exception.with_traceback(tb) – set self.__traceback__ to tb and return self.

exception ftrack_api.exception.ImmutableAttributeError(attribute, **kw)[source]

Raise when modification of immutable attribute attempted.

default_message = 'Cannot modify value of immutable {attribute.name!r} attribute.'
__init__(attribute, **kw)[source]

Initialise error.

args
with_traceback()

Exception.with_traceback(tb) – set self.__traceback__ to tb and return self.

exception ftrack_api.exception.CollectionError(collection, **kw)[source]

Raise when an error related to collections occurs.

default_message = 'Collection error.'
__init__(collection, **kw)[source]

Initialise error.

args
with_traceback()

Exception.with_traceback(tb) – set self.__traceback__ to tb and return self.

exception ftrack_api.exception.ImmutableCollectionError(collection, **kw)[source]

Raise when modification of immutable collection attempted.

default_message = 'Cannot modify value of immutable collection {collection!r}.'
__init__(collection, **kw)

Initialise error.

args
with_traceback()

Exception.with_traceback(tb) – set self.__traceback__ to tb and return self.

exception ftrack_api.exception.DuplicateItemInCollectionError(item, collection, **kw)[source]

Raise when duplicate item in collection detected.

default_message = 'Item {item!r} already exists in collection {collection!r}.'
__init__(item, collection, **kw)[source]

Initialise error.

args
with_traceback()

Exception.with_traceback(tb) – set self.__traceback__ to tb and return self.

exception ftrack_api.exception.ParseError(message=None, details=None)[source]

Raise when a parsing error occurs.

default_message = 'Failed to parse.'
__init__(message=None, details=None)

Initialise exception with message.

If message is None, the class ‘default_message’ will be used.

details should be a mapping of extra information that can be used in the message and also to provide more context.

args
with_traceback()

Exception.with_traceback(tb) – set self.__traceback__ to tb and return self.

exception ftrack_api.exception.EventHubError(message=None, details=None)[source]

Raise when issues related to event hub occur.

default_message = 'Event hub error occurred.'
__init__(message=None, details=None)

Initialise exception with message.

If message is None, the class ‘default_message’ will be used.

details should be a mapping of extra information that can be used in the message and also to provide more context.

args
with_traceback()

Exception.with_traceback(tb) – set self.__traceback__ to tb and return self.

exception ftrack_api.exception.EventHubConnectionError(message=None, details=None)[source]

Raise when event hub encounters connection problem.

default_message = 'Event hub is not connected.'
__init__(message=None, details=None)

Initialise exception with message.

If message is None, the class ‘default_message’ will be used.

details should be a mapping of extra information that can be used in the message and also to provide more context.

args
with_traceback()

Exception.with_traceback(tb) – set self.__traceback__ to tb and return self.

exception ftrack_api.exception.EventHubPacketError(message=None, details=None)[source]

Raise when event hub encounters an issue with a packet.

default_message = 'Invalid packet.'
__init__(message=None, details=None)

Initialise exception with message.

If message is None, the class ‘default_message’ will be used.

details should be a mapping of extra information that can be used in the message and also to provide more context.

args
with_traceback()

Exception.with_traceback(tb) – set self.__traceback__ to tb and return self.

exception ftrack_api.exception.PermissionDeniedError(message=None, details=None)[source]

Raise when permission is denied.

default_message = 'Permission denied.'
__init__(message=None, details=None)

Initialise exception with message.

If message is None, the class ‘default_message’ will be used.

details should be a mapping of extra information that can be used in the message and also to provide more context.

args
with_traceback()

Exception.with_traceback(tb) – set self.__traceback__ to tb and return self.

exception ftrack_api.exception.LocationError(message=None, details=None)[source]

Base for errors associated with locations.

default_message = 'Unspecified location error'
__init__(message=None, details=None)

Initialise exception with message.

If message is None, the class ‘default_message’ will be used.

details should be a mapping of extra information that can be used in the message and also to provide more context.

args
with_traceback()

Exception.with_traceback(tb) – set self.__traceback__ to tb and return self.

exception ftrack_api.exception.ComponentNotInAnyLocationError(message=None, details=None)[source]

Raise when component not available in any location.

default_message = 'Component not available in any location.'
__init__(message=None, details=None)

Initialise exception with message.

If message is None, the class ‘default_message’ will be used.

details should be a mapping of extra information that can be used in the message and also to provide more context.

args
with_traceback()

Exception.with_traceback(tb) – set self.__traceback__ to tb and return self.

exception ftrack_api.exception.ComponentNotInLocationError(components, location, **kw)[source]

Raise when component(s) not in location.

default_message = 'Component(s) {formatted_components} not found in location {location}.'
__init__(components, location, **kw)[source]

Initialise with components and location.

args
with_traceback()

Exception.with_traceback(tb) – set self.__traceback__ to tb and return self.

exception ftrack_api.exception.ComponentInLocationError(components, location, **kw)[source]

Raise when component(s) already exists in location.

default_message = 'Component(s) {formatted_components} already exist in location {location}.'
__init__(components, location, **kw)[source]

Initialise with components and location.

args
with_traceback()

Exception.with_traceback(tb) – set self.__traceback__ to tb and return self.

exception ftrack_api.exception.AccessorError(message=None, details=None)[source]

Base for errors associated with accessors.

default_message = 'Unspecified accessor error'
__init__(message=None, details=None)

Initialise exception with message.

If message is None, the class ‘default_message’ will be used.

details should be a mapping of extra information that can be used in the message and also to provide more context.

args
with_traceback()

Exception.with_traceback(tb) – set self.__traceback__ to tb and return self.

exception ftrack_api.exception.AccessorOperationFailedError(operation='', resource_identifier=None, error=None, **kw)[source]

Base for failed operations on accessors.

default_message = 'Operation {operation} failed: {error}'
__init__(operation='', resource_identifier=None, error=None, **kw)[source]

Initialise exception with message.

If message is None, the class ‘default_message’ will be used.

details should be a mapping of extra information that can be used in the message and also to provide more context.

args
with_traceback()

Exception.with_traceback(tb) – set self.__traceback__ to tb and return self.

exception ftrack_api.exception.AccessorUnsupportedOperationError(operation='', resource_identifier=None, error=None, **kw)[source]

Raise when operation is unsupported.

default_message = 'Operation {operation} unsupported.'
__init__(operation='', resource_identifier=None, error=None, **kw)

Initialise exception with message.

If message is None, the class ‘default_message’ will be used.

details should be a mapping of extra information that can be used in the message and also to provide more context.

args
with_traceback()

Exception.with_traceback(tb) – set self.__traceback__ to tb and return self.

exception ftrack_api.exception.AccessorPermissionDeniedError(operation='', resource_identifier=None, error=None, **kw)[source]

Raise when permission denied.

default_message = 'Cannot {operation} {resource_identifier}. Permission denied.'
__init__(operation='', resource_identifier=None, error=None, **kw)

Initialise exception with message.

If message is None, the class ‘default_message’ will be used.

details should be a mapping of extra information that can be used in the message and also to provide more context.

args
with_traceback()

Exception.with_traceback(tb) – set self.__traceback__ to tb and return self.

exception ftrack_api.exception.AccessorResourceIdentifierError(resource_identifier, **kw)[source]

Raise when a error related to a resource_identifier occurs.

default_message = 'Resource identifier is invalid: {resource_identifier}.'
__init__(resource_identifier, **kw)[source]

Initialise exception with message.

If message is None, the class ‘default_message’ will be used.

details should be a mapping of extra information that can be used in the message and also to provide more context.

args
with_traceback()

Exception.with_traceback(tb) – set self.__traceback__ to tb and return self.

exception ftrack_api.exception.AccessorFilesystemPathError(resource_identifier, **kw)[source]

Raise when a error related to an accessor filesystem path occurs.

default_message = 'Could not determine filesystem path from resource identifier: {resource_identifier}.'
__init__(resource_identifier, **kw)

Initialise exception with message.

If message is None, the class ‘default_message’ will be used.

details should be a mapping of extra information that can be used in the message and also to provide more context.

args
with_traceback()

Exception.with_traceback(tb) – set self.__traceback__ to tb and return self.

exception ftrack_api.exception.AccessorResourceError(operation='', resource_identifier=None, error=None, **kw)[source]

Base for errors associated with specific resource.

default_message = 'Unspecified resource error: {resource_identifier}'
__init__(operation='', resource_identifier=None, error=None, **kw)[source]

Initialise exception with message.

If message is None, the class ‘default_message’ will be used.

details should be a mapping of extra information that can be used in the message and also to provide more context.

args
with_traceback()

Exception.with_traceback(tb) – set self.__traceback__ to tb and return self.

exception ftrack_api.exception.AccessorResourceNotFoundError(operation='', resource_identifier=None, error=None, **kw)[source]

Raise when a required resource is not found.

default_message = 'Resource not found: {resource_identifier}'
__init__(operation='', resource_identifier=None, error=None, **kw)

Initialise exception with message.

If message is None, the class ‘default_message’ will be used.

details should be a mapping of extra information that can be used in the message and also to provide more context.

args
with_traceback()

Exception.with_traceback(tb) – set self.__traceback__ to tb and return self.

exception ftrack_api.exception.AccessorParentResourceNotFoundError(operation='', resource_identifier=None, error=None, **kw)[source]

Raise when a parent resource (such as directory) is not found.

default_message = 'Parent resource is missing: {resource_identifier}'
__init__(operation='', resource_identifier=None, error=None, **kw)

Initialise exception with message.

If message is None, the class ‘default_message’ will be used.

details should be a mapping of extra information that can be used in the message and also to provide more context.

args
with_traceback()

Exception.with_traceback(tb) – set self.__traceback__ to tb and return self.

exception ftrack_api.exception.AccessorResourceInvalidError(operation='', resource_identifier=None, error=None, **kw)[source]

Raise when a resource is not the right type.

default_message = 'Resource invalid: {resource_identifier}'
__init__(operation='', resource_identifier=None, error=None, **kw)

Initialise exception with message.

If message is None, the class ‘default_message’ will be used.

details should be a mapping of extra information that can be used in the message and also to provide more context.

args
with_traceback()

Exception.with_traceback(tb) – set self.__traceback__ to tb and return self.

exception ftrack_api.exception.AccessorContainerNotEmptyError(operation='', resource_identifier=None, error=None, **kw)[source]

Raise when container is not empty.

default_message = 'Container is not empty: {resource_identifier}'
__init__(operation='', resource_identifier=None, error=None, **kw)

Initialise exception with message.

If message is None, the class ‘default_message’ will be used.

details should be a mapping of extra information that can be used in the message and also to provide more context.

args
with_traceback()

Exception.with_traceback(tb) – set self.__traceback__ to tb and return self.

exception ftrack_api.exception.StructureError(message=None, details=None)[source]

Base for errors associated with structures.

default_message = 'Unspecified structure error'
__init__(message=None, details=None)

Initialise exception with message.

If message is None, the class ‘default_message’ will be used.

details should be a mapping of extra information that can be used in the message and also to provide more context.

args
with_traceback()

Exception.with_traceback(tb) – set self.__traceback__ to tb and return self.

exception ftrack_api.exception.ConnectionClosedError(message=None, details=None)[source]

Raise when attempt to use closed connection detected.

__init__(message=None, details=None)

Initialise exception with message.

If message is None, the class ‘default_message’ will be used.

details should be a mapping of extra information that can be used in the message and also to provide more context.

args
default_message = 'Connection closed.'
with_traceback()

Exception.with_traceback(tb) – set self.__traceback__ to tb and return self.

ftrack_api.formatter

ftrack_api.formatter.FILTER = {'ignore_unset': <function <lambda>>}

Useful filters to pass to format().`

ftrack_api.formatter.format(entity, formatters=None, attribute_filter=None, recursive=False, indent=0, indent_first_line=True, _seen=None)[source]

Return formatted string representing entity.

formatters can be used to customise formatting of elements. It should be a mapping with one or more of the following keys:

  • header - Used to format entity type.
  • label - Used to format attribute names.

Specify an attribute_filter to control which attributes to include. By default all attributes are included. The attribute_filter should be a callable that accepts (entity, attribute_name, attribute_value) and returns True if the attribute should be included in the output. For example, to filter out all unset values:

attribute_filter=ftrack_api.formatter.FILTER['ignore_unset']

If recursive is True then recurse into Collections and format each entity present.

indent specifies the overall indentation in spaces of the formatted text, whilst indent_first_line determines whether to apply that indent to the first generated line.

Warning

Iterates over all entity attributes which may cause multiple queries to the server. Turn off auto populating in the session to prevent this.

ftrack_api.inspection

ftrack_api.inspection.identity(entity)[source]

Return unique identity of entity.

ftrack_api.inspection.primary_key(entity)[source]

Return primary key of entity as an ordered mapping of {field: value}.

To get just the primary key values:

primary_key(entity).values()
ftrack_api.inspection.state(entity)[source]

Return current entity state.

ftrack_api.inspection.states(entities)[source]

Return current states of entities.

An optimised function for determining states of multiple entities in one go.

Note

All entities should belong to the same session.

ftrack_api.logging

ftrack_api.logging.deprecation_warning(message)[source]
class ftrack_api.logging.LazyLogMessage(message, *args, **kwargs)[source]

A log message that can be evaluated lazily for improved performance.

Example:

# Formatting of string will not occur unless debug logging enabled.
logger.debug(LazyLogMessage(
    'Hello {0}', 'world'
))
__init__(message, *args, **kwargs)[source]

Initialise with message format string and arguments.

ftrack_api.operation

class ftrack_api.operation.Operations[source]

Stack of operations.

__init__()[source]

Initialise stack.

clear()[source]

Clear all operations.

push(operation)[source]

Push operation onto stack.

pop()[source]

Pop and return most recent operation from stack.

class ftrack_api.operation.Operation[source]

Represent an operation.

__init__

Initialize self. See help(type(self)) for accurate signature.

class ftrack_api.operation.CreateEntityOperation(entity_type, entity_key, entity_data)[source]

Represent create entity operation.

__init__(entity_type, entity_key, entity_data)[source]

Initialise operation.

entity_type should be the type of entity in string form (as returned from ftrack_api.entity.base.Entity.entity_type).

entity_key should be the unique key for the entity and should follow the form returned from ftrack_api.inspection.primary_key().

entity_data should be a mapping of the initial data to populate the entity with when creating.

Note

Shallow copies will be made of each value in entity_data.

class ftrack_api.operation.UpdateEntityOperation(entity_type, entity_key, attribute_name, old_value, new_value)[source]

Represent update entity operation.

__init__(entity_type, entity_key, attribute_name, old_value, new_value)[source]

Initialise operation.

entity_type should be the type of entity in string form (as returned from ftrack_api.entity.base.Entity.entity_type).

entity_key should be the unique key for the entity and should follow the form returned from ftrack_api.inspection.primary_key().

attribute_name should be the string name of the attribute being modified and old_value and new_value should reflect the change in value.

Note

Shallow copies will be made of both old_value and new_value.

class ftrack_api.operation.DeleteEntityOperation(entity_type, entity_key)[source]

Represent delete entity operation.

__init__(entity_type, entity_key)[source]

Initialise operation.

entity_type should be the type of entity in string form (as returned from ftrack_api.entity.base.Entity.entity_type).

entity_key should be the unique key for the entity and should follow the form returned from ftrack_api.inspection.primary_key().

ftrack_api.plugin

ftrack_api.plugin.discover(paths, positional_arguments=None, keyword_arguments=None)[source]

Find and load plugins in search paths.

Each discovered module should implement a register function that accepts positional_arguments and keyword_arguments as *args and **kwargs respectively.

If a register function does not accept variable arguments, then attempt to only pass accepted arguments to the function by inspecting its signature.

ftrack_api.query

class ftrack_api.query.QueryResult(session, expression, page_size=500)[source]

Results from a query.

OFFSET_EXPRESSION = re.compile('(?P<offset>offset (?P<value>\\d+))')
LIMIT_EXPRESSION = re.compile('(?P<limit>limit (?P<value>\\d+))')
__init__(session, expression, page_size=500)[source]

Initialise result set.

session should be an instance of ftrack_api.session.Session that will be used for executing the query expression.

page_size should be an integer specifying the maximum number of records to fetch in one request allowing the results to be fetched incrementally in a transparent manner for optimal performance. Any offset or limit specified in expression are honoured for final result set, but intermediate queries may be issued with different offsets and limits in order to fetch pages. When an embedded limit is smaller than the given page_size it will be used instead and no paging will take place.

Warning

Setting page_size to a very large amount may negatively impact performance of not only the caller, but the server in general.

all()[source]

Fetch and return all data.

count(value) → integer -- return number of occurrences of value
index(value[, start[, stop]]) → integer -- return first index of value.

Raises ValueError if the value is not present.

Supporting start and stop arguments is optional, but recommended.

one()[source]

Return exactly one single result from query by applying a limit.

Raise ValueError if an existing limit is already present in the expression.

Raise ValueError if an existing offset is already present in the expression as offset is inappropriate when expecting a single item.

Raise MultipleResultsFoundError if more than one result was available or NoResultFoundError if no results were available.

Note

Both errors subclass IncorrectResultError if you want to catch only one error type.

first()[source]

Return first matching result from query by applying a limit.

Raise ValueError if an existing limit is already present in the expression.

If no matching result available return None.

ftrack_api.session

class ftrack_api.session.SessionAuthentication(api_key, api_user)[source]

Attach ftrack session authentication information to requests.

__init__(api_key, api_user)[source]

Initialise with api_key and api_user.

class ftrack_api.session.Session(server_url=None, api_key=None, api_user=None, auto_populate=True, plugin_paths=None, cache=None, cache_key_maker=None, auto_connect_event_hub=False, schema_cache_path=None, plugin_arguments=None)[source]

An isolated session for interaction with an ftrack server.

__init__(server_url=None, api_key=None, api_user=None, auto_populate=True, plugin_paths=None, cache=None, cache_key_maker=None, auto_connect_event_hub=False, schema_cache_path=None, plugin_arguments=None)[source]

Initialise session.

server_url should be the URL of the ftrack server to connect to including any port number. If not specified attempt to look up from FTRACK_SERVER.

api_key should be the API key to use for authentication whilst api_user should be the username of the user in ftrack to record operations against. If not specified, api_key should be retrieved from FTRACK_API_KEY and api_user from FTRACK_API_USER.

If auto_populate is True (the default), then accessing entity attributes will cause them to be automatically fetched from the server if they are not already. This flag can be changed on the session directly at any time.

plugin_paths should be a list of paths to search for plugins. If not specified, default to looking up FTRACK_EVENT_PLUGIN_PATH.

cache should be an instance of a cache that fulfils the ftrack_api.cache.Cache interface and will be used as the cache for the session. It can also be a callable that will be called with the session instance as sole argument. The callable should return None if a suitable cache could not be configured, but session instantiation can continue safely.

Note

The session will add the specified cache to a pre-configured layered cache that specifies the top level cache as a ftrack_api.cache.MemoryCache. Therefore, it is unnecessary to construct a separate memory cache for typical behaviour. Working around this behaviour or removing the memory cache can lead to unexpected behaviour.

cache_key_maker should be an instance of a key maker that fulfils the ftrack_api.cache.KeyMaker interface and will be used to generate keys for objects being stored in the cache. If not specified, a StringKeyMaker will be used.

If auto_connect_event_hub is True then embedded event hub will be automatically connected to the event server and allow for publishing and subscribing to non-local events. If False, then only publishing and subscribing to local events will be possible until the hub is manually connected using EventHub.connect.

Note

The event hub connection is performed in a background thread to improve session startup time. If a registered plugin requires a connected event hub then it should check the event hub connection status explicitly. Subscribing to events does not require a connected event hub.

Enable schema caching by setting schema_cache_path to a folder path. If not set, FTRACK_API_SCHEMA_CACHE_PATH will be used to determine the path to store cache in. If the environment variable is also not specified then a temporary directory will be used. Set to False to disable schema caching entirely.

plugin_arguments should be an optional mapping (dict) of keyword arguments to pass to plugin register functions upon discovery. If a discovered plugin has a signature that is incompatible with the passed arguments, the discovery mechanism will attempt to reduce the passed arguments to only those that the plugin accepts. Note that a warning will be logged in this case.

closed

Return whether session has been closed.

server_information

Return server information such as server version.

server_url

Return server ulr used for session.

api_user

Return username used for session.

api_key

Return API key used for session.

event_hub

Return event hub.

check_server_compatibility()[source]

Check compatibility with connected server.

close()[source]

Close session.

Close connections to server. Clear any pending operations and local cache.

Use this to ensure that session is cleaned up properly after use.

reset()[source]

Reset session clearing local state.

Clear all pending operations and expunge all entities from session.

Also clear the local cache. If the cache used by the session is a LayeredCache then only clear top level cache. Otherwise, clear the entire cache.

Plugins are not rediscovered or reinitialised, but certain plugin events are re-emitted to properly configure session aspects that are dependant on cache (such as location plugins).

Warning

Previously attached entities are not reset in memory and will retain their state, but should not be used. Doing so will cause errors.

auto_populating(auto_populate)[source]

Temporarily set auto populate to auto_populate.

The current setting will be restored automatically when done.

Example:

with session.auto_populating(False):
    print entity['name']
operation_recording(record_operations)[source]

Temporarily set operation recording to record_operations.

The current setting will be restored automatically when done.

Example:

with session.operation_recording(False):
    entity['name'] = 'change_not_recorded'
created

Return list of newly created entities.

modified

Return list of locally modified entities.

deleted

Return list of deleted entities.

reset_remote(reset_type, entity=None)[source]

Perform a server side reset.

reset_type is a server side supported reset type, passing the optional entity to perform the option upon.

Please refer to ftrack documentation for a complete list of supported server side reset types.

create(entity_type, data=None, reconstructing=False)[source]

Create and return an entity of entity_type with initial data.

If specified, data should be a dictionary of key, value pairs that should be used to populate attributes on the entity.

If reconstructing is False then create a new entity setting appropriate defaults for missing data. If True then reconstruct an existing entity.

Constructed entity will be automatically merged into the session.

ensure(entity_type, data, identifying_keys=None)[source]

Retrieve entity of entity_type with data, creating if necessary.

data should be a dictionary of the same form passed to create().

By default, check for an entity that has matching data. If identifying_keys is specified as a list of keys then only consider the values from data for those keys when searching for existing entity. If data is missing an identifying key then raise KeyError.

If no identifying_keys specified then use all of the keys from the passed data. Raise ValueError if no identifying_keys can be determined.

Each key should be a string.

Note

Currently only top level scalars supported. To ensure an entity by looking at relationships, manually issue the query() and create() calls.

If more than one entity matches the determined filter criteria then raise MultipleResultsFoundError.

If no matching entity found then create entity using supplied data.

If a matching entity is found, then update it if necessary with data.

Note

If entity created or updated then a commit() will be issued automatically. If this behaviour is undesired, perform the query() and create() calls manually.

Return retrieved or created entity.

Example:

# First time, a new entity with `username=martin` is created.
entity = session.ensure('User', {'username': 'martin'})

# After that, the existing entity is retrieved.
entity = session.ensure('User', {'username': 'martin'})

# When existing entity retrieved, entity may also be updated to
# match supplied data.
entity = session.ensure(
    'User', {'username': 'martin', 'email': 'martin@example.com'}
)
delete(entity)[source]

Mark entity for deletion.

get(entity_type, entity_key)[source]

Return entity of entity_type with unique entity_key.

First check for an existing entry in the configured cache, otherwise issue a query to the server.

If no matching entity found, return None.

query(expression, page_size=500)[source]

Query against remote data according to expression.

expression is not executed directly. Instead return an ftrack_api.query.QueryResult instance that will execute remote call on access.

page_size specifies the maximum page size that the returned query result object should be configured with.

See also

Querying

merge(value, merged=None)[source]

Merge value into session and return merged value.

merged should be a mapping to record merges during run and should be used to avoid infinite recursion. If not set will default to a dictionary.

populate(entities, projections)[source]

Populate entities with attributes specified by projections.

Any locally set values included in the projections will not be overwritten with the retrieved remote value. If this ‘synchronise’ behaviour is required, first clear the relevant values on the entity by setting them to ftrack_api.symbol.NOT_SET. Deleting the key will have the same effect:

>>> print(user['username'])
martin
>>> del user['username']
>>> print(user['username'])
Symbol(NOT_SET)

Note

Entities that have been created and not yet persisted will be skipped as they have no remote values to fetch.

commit()[source]

Commit all local changes to the server.

rollback()[source]

Clear all recorded operations and local state.

Typically this would be used following a failed commit() in order to revert the session to a known good state.

Newly created entities not yet persisted will be detached from the session / purged from cache and no longer contribute, but the actual objects are not deleted from memory. They should no longer be used and doing so could cause errors.

call(data)[source]

Make request to server with data batch describing the actions.

encode(data, entity_attribute_strategy='set_only')[source]

Return data encoded as JSON formatted string.

entity_attribute_strategy specifies how entity attributes should be handled. The following strategies are available:

  • all - Encode all attributes, loading any that are currently NOT_SET.
  • set_only - Encode only attributes that are currently set without loading any from the remote.
  • modified_only - Encode only attributes that have been modified locally.
  • persisted_only - Encode only remote (persisted) attribute values.
entity_reference(entity)[source]

Return entity reference that uniquely identifies entity.

Return a mapping containing the __entity_type__ of the entity along with the key, value pairs that make up it’s primary key.

decode(string)[source]

Return decoded JSON string as Python object.

pick_location(component=None)[source]

Return suitable location to use.

If no component specified then return highest priority accessible location. Otherwise, return highest priority accessible location that component is available in.

Return None if no suitable location could be picked.

pick_locations(components)[source]

Return suitable locations for components.

Return list of locations corresponding to components where each picked location is the highest priority accessible location for that component. If a component has no location available then its corresponding entry will be None.

create_component(path, data=None, location='auto')[source]

Create a new component from path with additional data

Note

This is a helper method. To create components manually use the standard Session.create() method.

path can be a string representing a filesystem path to the data to use for the component. The path can also be specified as a sequence string, in which case a sequence component with child components for each item in the sequence will be created automatically. The accepted format for a sequence is ‘{head}{padding}{tail} [{ranges}]’. For example:

'/path/to/file.%04d.ext [1-5, 7, 8, 10-20]'

data should be a dictionary of any additional data to construct the component with (as passed to Session.create()).

If location is specified then automatically add component to that location. The default of ‘auto’ will automatically pick a suitable location to add the component to if one is available. To not add to any location specifiy locations as None.

Note

A Session.commit may be automatically issued as part of the components registration in the location.

get_component_availability(component, locations=None)[source]

Return availability of component.

If locations is set then limit result to availability of component in those locations.

Return a dictionary of {location_id:percentage_availability}

get_component_availabilities(components, locations=None)[source]

Return availabilities of components.

If locations is set then limit result to availabilities of components in those locations.

Return a list of dictionaries of {location_id:percentage_availability}. The list indexes correspond to those of components.

get_widget_url(name, entity=None, theme=None)[source]

Return an authenticated URL for widget with name and given options.

The returned URL will be authenticated using a token which will expire after 6 minutes.

name should be the name of the widget to return and should be one of ‘info’, ‘tasks’ or ‘tasks_browser’.

Certain widgets require an entity to be specified. If so, specify it by setting entity to a valid entity instance.

theme sets the theme of the widget and can be either ‘light’ or ‘dark’ (defaulting to ‘dark’ if an invalid option given).

encode_media(media, version_id=None, keep_original='auto')[source]

Return a new Job that encode media to make it playable in browsers.

media can be a path to a file or a FileComponent in the ftrack.server location.

The job will encode media based on the file type and job data contains information about encoding in the following format:

{
    'output': [{
        'format': 'video/mp4',
        'component_id': 'e2dc0524-b576-11d3-9612-080027331d74'
    }, {
        'format': 'image/jpeg',
        'component_id': '07b82a97-8cf9-11e3-9383-20c9d081909b'
    }],
    'source_component_id': 'e3791a09-7e11-4792-a398-3d9d4eefc294',
    'keep_original': True
}

The output components are associated with the job via the job_components relation.

An image component will always be generated if possible that can be used as a thumbnail.

If media is a file path, a new source component will be created and added to the ftrack server location and a call to commit() will be issued. If media is a FileComponent, it will be assumed to be in available in the ftrack.server location.

If version_id is specified, the new components will automatically be associated with the AssetVersion. Otherwise, the components will not be associated to a version even if the supplied media belongs to one. A server version of 3.3.32 or higher is required for the version_id argument to function properly.

If keep_original is not set, the original media will be kept if it is a FileComponent, and deleted if it is a file path. You can specify True or False to change this behavior.

get_upload_metadata(component_id, file_name, file_size, checksum=None)[source]

Return URL and headers used to upload data for component_id.

file_name and file_size should match the components details.

The returned URL should be requested using HTTP PUT with the specified headers.

The checksum is used as the Content-MD5 header and should contain the base64-encoded 128-bit MD5 digest of the message (without the headers) according to RFC 1864. This can be used as a message integrity check to verify that the data is the same data that was originally sent.

send_user_invite(user)[source]

Send a invitation to the provided user.

user is a User instance

send_user_invites(users)[source]

Send a invitation to the provided user.

users is a list of User instances

send_review_session_invite(invitee)[source]

Send an invite to a review session to invitee.

invitee is a instance of ReviewSessionInvitee.

Note

The invitee must be committed.

send_review_session_invites(invitees)[source]

Send an invite to a review session to a list of invitees.

invitee is a list of ReviewSessionInvitee objects.

Note

All invitees must be committed.

class ftrack_api.session.AutoPopulatingContext(session, auto_populate)[source]

Context manager for temporary change of session auto_populate value.

__init__(session, auto_populate)[source]

Initialise context.

class ftrack_api.session.OperationRecordingContext(session, record_operations)[source]

Context manager for temporary change of session record_operations.

__init__(session, record_operations)[source]

Initialise context.

class ftrack_api.session.OperationPayload(*args, **kwargs)[source]

Represent operation payload.

__init__(*args, **kwargs)[source]

Initialise payload.

clear() → None. Remove all items from D.
get(k[, d]) → D[k] if k in D, else d. d defaults to None.
items() → a set-like object providing a view on D's items
keys() → a set-like object providing a view on D's keys
pop(k[, d]) → v, remove specified key and return the corresponding value.

If key is not found, d is returned if given, otherwise KeyError is raised.

popitem() → (k, v), remove and return some (key, value) pair

as a 2-tuple; but raise KeyError if D is empty.

setdefault(k[, d]) → D.get(k,d), also set D[k]=d if k not in D
update([E, ]**F) → None. Update D from mapping/iterable E and F.

If E present and has a .keys() method, does: for k in E: D[k] = E[k] If E present and lacks .keys() method, does: for (k, v) in E: D[k] = v In either case, this is followed by: for k, v in F.items(): D[k] = v

values() → an object providing a view on D's values

ftrack_api.symbol

class ftrack_api.symbol.Symbol(name, value=True)[source]

A constant symbol.

__init__(name, value=True)[source]

Initialise symbol with unique name and value.

value is used for nonzero testing.

ftrack_api.symbol.NOT_SET = Symbol(NOT_SET)

Symbol representing that no value has been set or loaded.

ftrack_api.symbol.CREATED = Symbol(CREATED)

Symbol representing created state.

ftrack_api.symbol.MODIFIED = Symbol(MODIFIED)

Symbol representing modified state.

ftrack_api.symbol.DELETED = Symbol(DELETED)

Symbol representing deleted state.

ftrack_api.symbol.COMPONENT_ADDED_TO_LOCATION_TOPIC = 'ftrack.location.component-added'

Topic published when component added to a location.

ftrack_api.symbol.COMPONENT_REMOVED_FROM_LOCATION_TOPIC = 'ftrack.location.component-removed'

Topic published when component removed from a location.

ftrack_api.symbol.ORIGIN_LOCATION_ID = 'ce9b348f-8809-11e3-821c-20c9d081909b'

Identifier of builtin origin location.

ftrack_api.symbol.UNMANAGED_LOCATION_ID = 'cb268ecc-8809-11e3-a7e2-20c9d081909b'

Identifier of builtin unmanaged location.

ftrack_api.symbol.REVIEW_LOCATION_ID = 'cd41be70-8809-11e3-b98a-20c9d081909b'

Identifier of builtin review location.

ftrack_api.symbol.CONNECT_LOCATION_ID = '07b82a97-8cf9-11e3-9383-20c9d081909b'

Identifier of builtin connect location.

ftrack_api.symbol.SERVER_LOCATION_ID = '3a372bde-05bc-11e4-8908-20c9d081909b'

Identifier of builtin server location.

ftrack_api.symbol.CHUNK_SIZE = 1048576

Chunk size used when working with data, default to 1Mb.

ftrack_api.symbol.JOB_SYNC_USERS_LDAP = Symbol(SYNC_USERS_LDAP)

Symbol representing syncing users with ldap

Event list

The following is a consolidated list of events published directly by this API.

For some events, a template plugin file is also listed for download (Download template plugin) to help get you started with writing your own plugin for a particular event.

ftrack.api.session.construct-entity-type

Download template plugin

Synchronous. Published by the session to retrieve constructed class for specified schema:

Event(
    topic='ftrack.api.session.construct-entity-type',
    data=dict(
        schema=schema,
        schemas=schemas
    )
)

Expects returned data to be:

A Python class.

ftrack.api.session.configure-location

Download template plugin

Synchronous. Published by the session to allow configuring of location instances:

Event(
    topic='ftrack.api.session.configure-location',
    data=dict(
        session=self
    )
)

ftrack.location.component-added

Published whenever a component is added to a location:

Event(
    topic='ftrack.location.component-added',
    data=dict(
        component_id='e2dc0524-b576-11d3-9612-080027331d74',
        location_id='07b82a97-8cf9-11e3-9383-20c9d081909b'
    )
)

ftrack.location.component-removed

Published whenever a component is removed from a location:

Event(
    topic='ftrack.location.component-removed',
    data=dict(
        component_id='e2dc0524-b576-11d3-9612-080027331d74',
        location_id='07b82a97-8cf9-11e3-9383-20c9d081909b'
    )
)

ftrack.api.session.ready

Synchronous. Published after a Session has been initialized and is ready to be used:

Event(
    topic='ftrack.api.session.ready',
    data=dict(
        session=<Session instance>,
    )
)

Warning

Since the event is synchronous and blocking, avoid doing any unnecessary work as it will slow down session initialization.

See also

Also see example usage in example_plugin_using_session.py.

ftrack.api.session.reset

Synchronous. Published after a Session has been reset and is ready to be used again:

Event(
    topic='ftrack.api.session.reset',
    data=dict(
        session=<Session instance>,
    )
)

Environment variables

The following is a consolidated list of environment variables that this API can reference:

FTRACK_SERVER

The full url of the ftrack server to connect to. For example “https://mycompany.ftrackapp.com

FTRACK_API_USER

The username of the ftrack user to act on behalf of when performing actions in the system.

Note

When this environment variable is not set, the API will typically also check other standard operating system variables that hold the username of the current logged in user. To do this it uses getpass.getuser().

FTRACK_API_KEY

The API key to use when performing actions in the system. The API key is used to determine the permissions that a script has in the system.

FTRACK_APIKEY

For backwards compatibility. See FTRACK_API_KEY.

FTRACK_EVENT_PLUGIN_PATH

Paths to search recursively for plugins to load and use in a session. Multiple paths can be specified by separating with the value of os.pathsep (e.g. ‘:’ or ‘;’).

FTRACK_API_SCHEMA_CACHE_PATH

Path to a directory that will be used for storing and retrieving a cache of the entity schemas fetched from the server.

http_proxy / https_proxy

If you need to use a proxy to connect to ftrack you can use the “standard” http_proxy and https_proxy. Please note that they are lowercase.

For example “export https_proxy=http://proxy.mycompany.com:8080”

Security and authentication

Self signed SSL certificate

When using a self signed SSL certificate the API may fail to connect if it cannot verify the SSL certificate. Under the hood the requests library is used and it must be specified where the trusted certificate authority can be found using the environment variable REQUESTS_CA_BUNDLE.

InsecurePlatformWarning

When using this API you may sometimes see a warning:

InsecurePlatformWarning: A true SSLContext object is not available. This
prevents urllib3 from configuring SSL appropriately and may cause certain
SSL connections to fail.

If you encounter this warning, its recommended you upgrade to Python 2.7.9, or use pyOpenSSL. To use pyOpenSSL simply:

pip install pyopenssl ndg-httpsclient pyasn1

and the requests library used by this API will use pyOpenSSL instead.

Release and migration notes

Find out information about what has changed between versions and any important migration notes to be aware of when switching to a new version.

Release Notes

2.0.0

6 May 2020
  • new

    DocumentationAdded new section Set attributes on new entities to custom attributes known limitation.

  • fixed

    testsNote reply randomly fails.

  • fixed

    eventssessionEvent server wait method will sometimes raise a connection error because of a race condition.

  • changed

    websocketSelect highest available protocol version when connecting to websocket.

  • changed

    versionReplace fixed version with automatic versioning from git repository.

  • changed

    eventsNotify users on remote event server connection status while waiting.

  • fix

    Transfer component from server storage breaks due to different string/byte handling.

  • changed

    SessionPrivate method Session._entity_reference() has been deprecated.

    Note

    You should now use the public method Session.entity_reference().

  • changed

    SessionPrivate method Session._call() has been deprecated.

    Note

    You should now use the public method Session.call().

  • changed

    SessionPublic method Session.delayed_job() has been deprecated.

    Note

    You should now use Session.call().

  • fixed

    ApiLocationLocationMixins are not compatible with Python 3.

  • fixed

    TestEntity test fails due to missing parents.

  • new

    TestsAdd support for flaky tests to improve test reliability.

  • changed

    SessionDo not auto connect by default to event server hub.

  • new

    Provide support for python3.X.

1.8.2

14 January 2020
  • fixed

    Testtest_ensure_entity_with_non_string_data_types test fails due to missing parents.

  • changed

    sessionUse WeakMethod when registering atexit handler to prevent memory leak.

1.8.1

30 October 2019
  • changed

    LocationIncrease chunk size for file operations to 1 Megabyte. This value can now also be set from the environment variable:

    FTRACK_API_FILE_CHUNK_SIZE

  • new

    setupAdd check for correct python version when installing with pip.

  • new

    NotesAdd support for note labels in create_note helper method.

  • changed

    sessionEnsure errors from server are fully reported with stack trace.

1.8.0

21 February 2019
  • fixed

    documentationEvent description component-removed report component-added event signature.

  • new

    attributesessionAdd new scalar type object to factory.

  • new

    attributesessionAdd support for list of computed attributes as part of schema definition. A computed attribute is derived on the server side, and can be time dependentant and differ between users. As such a computed attribute is not suitable for long term encoding and will not be encoded with the persisted_only stragey.

  • changed

    The delayed_job method has been deprecated in favour of a direct Session.call. See Sync users with LDAP for example usage.

  • changed

    Private method Session._call() has been converted to a public method, Session.call().

    The private method will continue to work, but a pending deprecation warning will be issued when used. The private method will be removed entirely in version 2.0.

  • changed

    eventssessionEvent server connection error is too generic, the actual error is now reported to users.

1.7.1

13 November 2018
  • fixed

    eventssessionMeta events for event hub connect and disconnect does not include source.

  • fixed

    locationsessionMissing context argument to ResourceIdentifierTransformer.decode() in Location.get_resource_identifier().

1.7.0

27 July 2018
  • new

    eventssessionAdded new events ftrack.api.session.ready and ftrack.api.session.reset which can be used to perform operations after the session is ready or has been reset, respectively.

  • changed

    Private method Session._entity_reference() has been converted to a public method, Session.entity_reference().

    The private method will continue to work, but a pending deprecation warning will be issued when used. The private method will be removed entirely in version 2.0.

  • fixed

    eventssessionSession.close() raises an exception if event hub was explicitly connected after session initialization.

1.6.0

17 May 2018
  • new

    depreciationeventsIn version 2.0.0 of the ftrack-python-api the default behavior for the Session class will change for the argument auto_connect_event_hub, the default value will switch from True to False.

    A warning will now be emitted if async events are published or subscribed to without auto_connect_event_hub has not explicitly been set to True.

  • fixed

    documentationEvent payload not same as what is being emitted for ftrack.location.component-added and ftrack.location.component-removed.

  • fixed

    eventsPyparsing is causing random errors in a threaded environment.

1.5.0

19 April 2018
  • fixed

    cachesessionCached entities not updated correctly when fetched in a nested query.

1.4.0

5 February 2018

1.3.3

16 November 2017
  • new

    ldapusersAdd support for triggering a synchronization of users between ldap and ftrack. See Sync users with LDAP for example usage.

    Note

    This requires that you run ftrack 3.5.10 or later.

  • fixed

    Not possible to set metadata on creation.

1.3.2

18 September 2017
  • new

    task templateAdded example for managing task templates through the API. See Working with Task Templates for example usage.

  • fixed

    custom attributes

    Not possible to set hierarchical custom attributes on an entity that has not been committed.

  • fixed

    custom attributes

    Not possible to set custom attributes on an Asset that has not been committed.

  • fixed

    Not possible to set metadata on creation.

1.3.1

21 July 2017
  • fixed

    eventssessionCalling disconnect on the event hub is slow.

1.3.0

17 July 2017
  • new

    sessionSupport using a Session as a context manager to aid closing of session after use:

    with ftrack_api.Session() as session:
        # Perform operations with session.
    
  • new

    sessionSession.close() automatically called on Python exit if session not already closed.

  • new

    sessionAdded Session.close() to properly close a session’s connections to the server(s) as well as ensure event listeners are properly unsubscribed.

  • new

    Added ftrack_api.exception.ConnectionClosedError to represent error caused when trying to access servers over closed connection.

1.2.0

16 June 2017
  • changed

    eventsUpdated the websocket-client dependency to version >= 0.40.0 to allow for http proxies.

  • fixed

    documentationThe Publishing versions example incorrectly stated that a location would be automatically picked if the location keyword argument was omitted.

1.1.1

27 April 2017
  • fixed

    custom attributesCannot use custom attributes for Asset in ftrack versions prior to 3.5.0.

  • fixed

    documentationThe example section for managing text custom attributes is not correct.

1.1.0

8 March 2017
  • new

    server locationthumbnailAdded method get_thumbnail_url() to server location, which can be used to retrieve a thumbnail URL. See Retrieving thumbnail URL for example usage.

  • new

    documentationAdded example on how to manage entity links from the API.

  • new

    documentationAdded example on how to manage custom attribute configurations from the API.

  • new

    documentationAdded example on how to use SecurityRole and UserSecurityRole to manage security roles for users.

  • new

    documentationAdded examples to show how to list a user’s assigned tasks and all users assigned to a task.

  • changed

    pluginssessionAdded plugin_arguments to Session to allow passing of optional keyword arguments to discovered plugin register functions. Only arguments defined in a plugin register function signature are passed so existing plugin register functions do not need updating if the new functionality is not desired.

  • fixed

    documentationThe Working with projects example can be confusing since the project schema may not contain the necessary object types.

  • fixed

    documentationQuery tutorial article gives misleading information about the has operator.

  • fixed

    sessionSize is not set on sequence components when using Session.create_component().

1.0.4

13 January 2017
  • fixed

    custom attributesCustom attribute values cannot be set on entities that are not persisted.

  • fixed

    eventsusername in published event’s source data is set to the operating system user and not the API user.

1.0.3

4 January 2017
  • changed

    custom attributessessionIncreased performance of custom attributes and better support for filtering when using a version of ftrack that supports non-sparse attribute values.

  • changed

    custom attributessessionCustom attributes can no longer be set by mutating entire dictionary.

1.0.2

17 November 2016
  • changed

    sessionRemoved version restriction for higher server versions.

1.0.1

11 November 2016
  • fixed

    EventHub.publish on_reply callback only called for first received reply. It should be called for all relevant replies received.

1.0.0

28 October 2016
  • new

    sessionSession.get_upload_metadata() has been added.

  • changed

    backwards-incompatiblelocationsData transfer between locations using accessors is now chunked to avoid reading large files into memory.

  • changed

    server accessorftrack_api.accessor.server.ServerFile has been refactored to work with large files more efficiently.

  • changed

    server accessorftrack_api.accessor.server.ServerFile has been updated to use the get_upload_metadata API endpoint instead of /component/getPutMetadata.

  • changed

    locationsftrack_api.data.String is now using a temporary file instead of StringIO to avoid reading large files into memory.

  • fixed

    locationssessionftrack.centralized-storage does not properly validate location selection during user configuration.

0.16.0

18 October 2016
  • new

    encode mediasessionSession.encode_media() can now automatically associate the output with a version by specifying a version_id keyword argument. A new helper method on versions, AssetVersion.encode_media, can be used to make versions playable in a browser. A server version of 3.3.32 or higher is required for it to function properly.

    See also

    Encoding media.

  • changed

    encode mediasessionYou can now decide if Session.encode_media() should keep or delete the original component by specifying the keep_original keyword argument.

  • changed

    backwards-incompatiblecollectionCollection mutation now stores collection instance in operations rather than underlying data structure.

  • changed

    performanceImprove performance of commit operations by optimising encoding and reducing payload sent to server.

  • fixed

    documentationAsset parent variable is declared but never used in Publishing versions.

  • fixed

    documentationDocumentation of hierarchical attributes and their limitations are misleading. See Using custom attributes.

0.15.5

12 August 2016

0.15.4

12 July 2016
  • fixed

    queryingCustom offset not respected by QueryResult.first.

  • changed

    queryingUsing a custom offset with QueryResult.one helper method now raises an exception as an offset is inappropriate when expecting to select a single item.

  • fixed

    cachingLayeredCache.remove incorrectly raises KeyError if key only exists in sub-layer cache.

0.15.3

30 June 2016
  • fixed

    cachingsessionA newly created entity now has the correct ftrack_api.symbol.CREATED state when checked in caching layer. Previously the state was ftrack_api.symbol.NOT_SET. Note that this fix causes a change in logic and the stored ftrack_api.operation.CreateEntityOperation might hold data that has not been fully merged.

  • fixed

    documentationThe second example in the assignments article is not working.

  • changed

    cachingsessionA callable cache maker can now return None to indicate that it could not create a suitable cache, but Session instantiation can continue safely.

0.15.2

2 June 2016
  • new

    documentationAdded an example on how to work with assignments and allocations Working with assignments and allocations.

  • new

    documentationAdded Using entity links article with examples of how to manage asset version dependencies.

  • fixed

    performanceImprove performance of large collection management.

  • fixed

    Entities are not hashable because ftrack_api.entity.base.Entity.__hash__() raises TypeError.

0.15.1

2 May 2016
  • fixed

    attributecollectionperformanceCustom attribute configurations does not cache necessary keys, leading to performance issues.

  • fixed

    locationsstructureStandard structure does not work if version relation is not set on the Component.

0.15.0

4 April 2016
  • new

    locationssessionftrack.centralized-storage not working properly on Windows.

0.14.0

14 March 2016
  • changed

    locationssessionThe ftrack.centralized-storage configurator now validates that name, label and description for new locations are filled in.

  • new

    client reviewsessionAdded Session.send_review_session_invite() and Session.send_review_session_invites() that can be used to inform review session invitees about a review session.

    See also

    Usage guide.

  • new

    locationssessionAdded ftrack.centralized-storage configurator as a private module. It implements a wizard like interface used to configure a centralised storage scenario.

  • new

    locationssessionftrack.centralized-storage storage scenario is automatically configured based on information passed from the server with the query_server_information action.

  • new

    structureAdded ftrack_api.structure.standard.StandardStructure with hierarchy based resource identifier generation.

  • new

    documentationAdded more information to the Configuring plugins article.

  • fixed

    start_timer() arguments comment and name are ignored.

  • fixed

    stop_timer() calculates the wrong duration when the server is not running in UTC.

    For the duration to be calculated correctly ftrack server version >= 3.3.15 is required.

0.13.0

10 February 2016
  • new

    componentthumbnailAdded improved support for handling thumbnails.

  • new

    encode mediasessionAdded Session.encode_media() that can be used to encode media to make it playable in a browser.

    See also

    Encoding media.

  • fixed

    Session.commit() fails when setting a custom attribute on an asset version that has been created and committed in the same session.

  • new

    locationsAdded ftrack_api.entity.location.Location.get_url() to retrieve a URL to a component in a location if supported by the ftrack_api.accessor.base.Accessor.

  • new

    documentationUpdated Using notes and Managing jobs articles with examples of how to use note and job components.

  • changed

    loggingperformanceLogged messages now evaluated lazily using ftrack_api.logging.LazyLogMessage as optimisation.

  • changed

    eventssessionAuto connection of event hub for Session now takes place in background to improve session startup time.

  • changed

    eventssessionEvent hub connection timeout is now 60 seconds instead of 10.

  • changed

    server versionftrack server version >= 3.3.11, < 3.4 required.

  • changed

    performancequeryingftrack_api.query.QueryResult now pages internally using a specified page size in order to optimise record retrieval for large query results. Session.query() has also been updated to allow passing a custom page size at runtime if desired.

  • changed

    performancequeryingIncreased performance of first() and one() by using new limit syntax.

0.12.0

17 December 2015

0.11.0

4 December 2015
  • new

    documentationUpdated Migrating from old API with new link attribute and added a usage example.

  • new

    cachingperformanceschemasCaching of schemas for increased performance. ftrack_api.session.Session() now accepts schema_cache_path argument to specify location of schema cache. If not set it will use a temporary folder.

0.10.0

24 November 2015
  • changed

    testsUpdated session test to use mocked schemas for encoding tests.

  • fixed

    Documentation specifies Python 2.6 instead of Python 2.7 as minimum interpreter version.

  • fixed

    Documentation does not reflect current dependencies.

  • changed

    componentlocationsperformancesessionImproved performance of ftrack_api.entity.location.Location.add_components() by batching database operations.

    As a result it is no longer possible to determine progress of transfer for container components in realtime as events will be emitted in batch at end of operation.

    In addition, it is now the callers responsibility to clean up any transferred data should an error occur during either data transfer or database registration.

  • changed

    exceptionlocationsftrack_api.exception.ComponentInLocationError now accepts either a single component or multiple components and makes them available as components in its details parameter.

  • changed

    testsUpdated session test to not fail on the new private link attribute.

  • changed

    sessionInternal method _fetch_schemas() has beed renamed to Session._load_schemas() and now requires a schema_cache_path argument.

0.9.0

30 October 2015
  • new

    cachingAdded ftrack_api.cache.Cache.values() as helper for retrieving all values in cache.

  • fixed

    cachingsessionSession.merge() redundantly attempts to expand entity references that have already been expanded causing performance degradation.

  • new

    sessionSession.rollback() has been added to support cleanly reverting session state to last good state following a failed commit.

  • changed

    eventsEvent hub will no longer allow unverified SSL connections.

  • changed

    sessionSession.reset() no longer resets the connection. It also clears all local state and re-configures certain aspects that are cache dependant, such as location plugins.

  • fixed

    factoryDebug logging messages using incorrect index for formatting leading to misleading exception.

0.8.4

8 October 2015

0.8.3

28 September 2015
  • changed

    server versionftrack server version >= 3.2.1, < 3.4 required.

  • changed

    Updated ftrack.server location implementation. A server version of 3.3 or higher is required for it to function properly.

  • fixed

0.8.2

16 September 2015

0.8.1

8 September 2015

0.8.0

28 August 2015
  • changed

    server versionftrack server version >= 3.2.1, < 3.3 required.

  • new

    Added lists example.

    See also

    Using lists.

  • new

    Added convenience methods for handling timers start_timer and stop_timer.

  • changed

    The dynamic API classes Type, Status, Priority and StatusType have been renamed to Type, Status, Priority and State.

  • changed

    Session.reset() now also clears the top most level cache (by default a MemoryCache).

  • fixed

    Some invalid server url formats not detected.

  • fixed

    Reply events not encoded correctly causing them to be misinterpreted by the server.

0.7.0

24 August 2015
  • changed

    server versionftrack server version >= 3.2, < 3.3 required.

  • changed

    Removed automatic set of default statusid, priorityid and typeid on objects as that is now either not mandatory or handled on server.

  • changed

    Updated get_statuses() and get_types() to handle custom objects.

0.6.0

19 August 2015
  • changed

    server versionftrack server version >= 3.1.8, < 3.2 required.

  • changed

    documentationqueryingUpdated documentation with details on new operators has and any for querying relationships.

0.5.2

29 July 2015
  • changed

    server versionftrack server version 3.1.5 or greater required.

  • changed

    Server reported errors are now more readable and are no longer sometimes presented as an HTML page.

0.5.1

6 July 2015
  • changed

    Defaults computed by StandardFactory are now memoised per session to improve performance.

  • changed

    Memoiser now supports a return_copies parameter to control whether deep copies should be returned when a value was retrieved from the cache.

0.5.0

2 July 2015
  • changed

    Now checks for server compatibility and requires an ftrack server version of 3.1 or greater.

  • new

    Added convenience methods to QueryResult to fetch first() or exactly one() result.

  • new

    notesAdded support for handling notes.

    See also

    Using notes.

  • changed

    Collection attributes generate empty collection on first access when no remote value available. This allows interacting with a collection on a newly created entity before committing.

  • fixed

    sessionAmbiguous error raised when Session is started with an invalid user or key.

  • fixed

    cachingsessionSession.merge() fails against SerialisedCache when circular reference encountered due to entity identity not being prioritised in merge.

0.4.3

29 June 2015
  • fixed

    entity typespluginssessionEntity types not constructed following standard install.

    This is because the discovery of the default plugins is unreliable across Python installation processes (pip, wheel etc). Instead, the default plugins have been added as templates to the Event list documentation and the StandardFactory used to create any missing classes on Session startup.

0.4.2

26 June 2015
  • fixed

    Setting exact same metadata twice can cause ImmutableAttributeError to be incorrectly raised.

  • fixed

    sessionCalling Session.commit() does not clear locally set attribute values leading to immutability checks being bypassed in certain cases.

0.4.1

25 June 2015
  • fixed

    Setting metadata twice in one session causes KeyError.

0.4.0

22 June 2015
  • changed

    documentationDocumentation extensively updated.

  • new

    Client reviewAdded support for handling review sessions.

    See also

    Usage guide.

  • fixed

    Metadata property not working in line with rest of system, particularly the caching framework.

  • new

    collectionAdded ftrack_api.collection.MappedCollectionProxy class for providing a dictionary interface to a standard ftrack_api.collection.Collection.

  • new

    attributecollectionAdded ftrack_api.attribute.MappedCollectionAttribute class for describing an attribute that should use the ftrack_api.collection.MappedCollectionProxy.

  • new

    Entities that use composite primary keys are now fully supported in the session, including for Session.get() and Session.populate().

  • change

    Base ftrack_api.entity.factory.Factory refactored to separate out attribute instantiation into dedicated methods to make extending simpler.

  • change

    attributecollectionftrack_api.attribute.DictionaryAttribute and ftrack_api.attribute.DictionaryAttributeCollection removed. They have been replaced by the new ftrack_api.attribute.MappedCollectionAttribute and ftrack_api.collection.MappedCollectionProxy respectively.

  • new

    eventsSession now supports an auto_connect_event_hub argument to control whether the built in event hub should connect to the server on session initialisation. This is useful for when only local events should be supported or when the connection should be manually controlled.

0.3.0

14 June 2015
  • fixed

    Session operations may be applied server side in invalid order resulting in unexpected error.

  • fixed

    Creating and deleting an entity in single commit causes error as create operation never persisted to server.

    Now all operations for the entity are ignored on commit when this case is detected.

  • changed

    Internally moved from differential state to operation tracking for determining session changes when persisting.

  • new

    Session.recorded_operations attribute for examining current pending operations on a Session.

  • new

    Session.operation_recording() context manager for suspending recording operations temporarily. Can also manually control Session.record_operations boolean.

  • new

    Operation classes to track individual operations occurring in session.

  • new

    Public Session.merge() method for merging arbitrary values into the session manually.

  • changed

    An entity’s state is now computed from the operations performed on it and is no longer manually settable.

  • changed

    Entity.state attribute removed. Instead use the new inspection ftrack_api.inspection.state().

    Previously:

    print entity.state
    

    Now:

    import ftrack_api.inspection
    print ftrack_api.inspection.state(entity)
    

    There is also an optimised inspection, ftrack_api.inspection.states(). for determining state of many entities at once.

  • changed

    Shallow copying a ftrack_api.symbol.Symbol instance now returns same instance.

0.2.0

4 June 2015
  • changed

    Changed name of API from ftrack to ftrack_api.

    See also

    New API name.

  • new

    cachingConfigurable caching support in Session, including the ability to use an external persisted cache and new cache implementations.

    See also

    Caching.

  • new

    cachingSession.get() now tries to retrieve matching entity from configured cache first.

  • new

    cachingserialisationSession.encode() supports a new mode persisted_only that will only encode persisted attribute values.

  • changed

    Session.merge method is now private (Session._merge()) until it is qualified for general usage.

  • changed

    entity stateEntity state now managed on the entity directly rather than stored separately in the Session.

    Previously:

    session.set_state(entity, state)
    print session.get_state(entity)
    

    Now:

    entity.state = state
    print entity.state
    
  • changed

    entity stateEntity states are now ftrack_api.symbol.Symbol instances rather than strings.

    Previously:

    entity.state = 'created'
    

    Now:

    entity.state = ftrack_api.symbol.CREATED
    
  • fixed

    entity stateIt is now valid to transition from most entity states to an ftrack_api.symbol.NOT_SET state.

  • changed

    cachingEntityKeyMaker removed and replaced by StringKeyMaker. Entity identity now computed separately and passed to key maker to allow key maker to work with non entity instances.

  • fixed

    entityInternal data keys ignored when re/constructing entities reducing distracting and irrelevant warnings in logs.

  • fixed

    entityEntity equality test raises error when other is not an entity instance.

  • changed

    cachingentitymerge() now also merges state and local attributes. In addition, it ensures values being merged have also been merged into the session and outputs more log messages.

  • fixed

    inspectionftrack_api.inspection.identity() returns different result for same entity depending on whether entity type is unicode or string.

  • fixed

    ftrack_api.mixin() causes method resolution failure when same class mixed in multiple times.

  • changed

    Representations of objects now show plain id rather than converting to hex.

  • fixed

    eventsEvent hub raises TypeError when listening to ftrack.update events.

  • fixed

    eventsftrack_api.event.hub.EventHub.subscribe() fails when subscription argument contains special characters such as @ or +.

  • fixed

    collectionftrack_api.collection.Collection() incorrectly modifies entity state on initialisation.

0.1.0

25 March 2015
  • changed

    Moved standardised construct entity type logic to core package (as part of the StandardFactory) for easier reuse and extension.

0.1.0-beta.2

17 March 2015
  • new

    locationsSupport for ftrack.server location. The corresponding server build is required for it to function properly.

  • new

    locationsSupport for managing components in locations has been added. Check out the dedicated tutorial.

  • new

    A new inspection API (ftrack_api.inspection) has been added for extracting useful information from objects in the system, such as the identity of an entity.

  • changed

    Entity.primary_key and Entity.identity have been removed. Instead, use the new ftrack_api.inspection.primary_key() and ftrack_api.inspection.identity() functions. This was done to make it clearer the the extracted information is determined from the current entity state and modifying the returned object will have no effect on the entity instance itself.

  • changed

    ftrack_api.inspection.primary_key() now returns a mapping of the attribute names and values that make up the primary key, rather than the previous behaviour of returning a tuple of just the values. To emulate previous behaviour do:

    ftrack_api.inspection.primary_key(entity).values()
    
  • changed

    Session.encode() now supports different strategies for encoding entities via the entity_attribute_strategy* keyword argument. This makes it possible to use this method for general serialisation of entity instances.

  • changed

    Encoded referenced entities are now a mapping containing __entity_type__ and then each key, value pair that makes up the entity’s primary key. For example:

    {
        '__entity_type__': 'User',
        'id': '8b90a444-4e65-11e1-a500-f23c91df25eb'
    }
    
  • changed

    Session.decode() no longer automatically adds decoded entities to the Session cache making it possible to use decode independently.

  • new

    Added Session.merge() for merging entities recursively into the session cache.

  • fixed

    Replacing an entity in a ftrack_api.collection.Collection with an identical entity no longer raises ftrack_api.exception.DuplicateItemInCollectionError.

Migration notes

Note

Migrating from the old ftrack API? Read the dedicated guide.

Migrate to 2.0.0

Default behavior for connecting to event hub

The default behavior for the ftrack_api.Session class has changed for the argument auto_connect_event_hub, the default value has switched from True to False. In order for code relying on the event hub to continue functioning as expected you must modify your code to explicitly set the argument to True or that you manually call session.event_hub.connect().

Note

If you rely on the ftrack.location.component-added or ftrack.location.component-removed events to further process created or deleted components remember that your session must be connected to the event hub for the events to be published.

Migrate to 1.0.3

Mutating custom attribute dictionary

Custom attributes can no longer be set by mutating entire dictionary:

# This will result in an error.
task['custom_attributes'] = dict(foo='baz', bar=2)
session.commit()

Instead the individual values should be changed:

# This works better.
task['custom_attributes']['foo'] = 'baz'
task['custom_attributes']['bar'] = 2
session.commit()

Migrate to 1.0.0

Chunked accessor transfers

Data transfers between accessors is now buffered using smaller chunks instead of all data at the same time. Included accessor file representations such as ftrack_api.data.File and ftrack_api.accessor.server.ServerFile are built to handle that. If you have written your own accessor and file representation you may have to update it to support multiple reads using the limit parameter and multiple writes.

Migrate to 0.2.0

New API name

In this release the API has been renamed from ftrack to ftrack_api. This is to allow both the old and new API to co-exist in the same environment without confusion.

As such, any scripts using this new API need to be updated to import ftrack_api instead of ftrack. For example:

Previously:

import ftrack
import ftrack.formatter
...

Now:

import ftrack_api
import ftrack_api.formatter
...

Migrating from old API

Why a new API?

With the introduction of Workflows, ftrack is capable of supporting a greater diversity of industries. We’re enabling teams to closely align the system with their existing practices and naming conventions, resulting in a tool that feels more natural and intuitive. The old API was locked to specific workflows, making it impractical to support this new feature naturally.

We also wanted this new flexibility to extend to developers, so we set about redesigning the API to fully leverage the power in the system. And while we had the wrenches out, we figured why not go that extra mile and build in some of the features that we see developers having to continually implement in-house across different companies - features such as caching and support for custom pipeline extensions. In essence, we decided to build the API that, as pipeline developers, we had always wanted from our production tracking and asset management systems. We think we succeeded, and we hope you agree.

Installing

Before, you used to download the API package from your ftrack instance. With each release of the new API we make it available on PyPi, and installing is super simple:

pip install ftrack-python-api

Before installing, it is always good to check the latest Release Notes to see which version of the ftrack server is required.

See also

Installing

Overview

An API needs to be approachable, so we built the new API to feel intuitive and familiar. We bundle all the core functionality into one place – a session – with consistent methods for interacting with entities in the system:

import ftrack_api
session = ftrack_api.Session()

The session is responsible for loading plugins and communicating with the ftrack server and allows you to use multiple simultaneous sessions. You will no longer need to explicitly call ftrack.setup() to load plugins.

The core methods are straightforward:

Session.create
create a new entity, like a new version.
Session.query
fetch entities from the server using a powerful query language.
Session.delete
delete existing entities.
Session.commit
commit all changes in one efficient call.

Note

The new API batches create, update and delete operations by default for efficiency. To synchronise local changes with the server you need to call Session.commit().

In addition all entities in the API now act like simple Python dictionaries, with some additional helper methods where appropriate. If you know a little Python (or even if you don’t) getting up to speed should be a breeze:

>>> print user.keys()
['first_name', 'last_name', 'email', ...]
>>> print user['email']
'old@example.com'
>>> user['email'] = 'new@example.com'

And of course, relationships between entities are reflected in a natural way as well:

new_timelog = session.create('Timelog', {...})
task['timelogs'].append(new_timelog)

See also

Tutorial

The new API also makes use of caching in order to provide more efficient retrieval of data by reducing the number of calls to the remote server.

See also

Caching

Open source and standard code style

The new API is open source software and developed in public at Bitbucket. We welcome you to join us in the development and create pull requests there.

In the new API, we also follow the standard code style for Python, PEP-8. This means that you will now find that methods and variables are written using snake_case instead of camelCase, amongst other things.

Package name

The new package is named ftrack_api. By using a new package name, we enable you to use the old API and the new side-by-side in the same process.

Old API:

import ftrack

New API:

import ftrack_api

Specifying your credentials

The old API used three environment variables to authenticate with your ftrack instance. While these continue to work as before, you now also have the option to specify them when initializing the session:

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

In the examples below, will assume that you have imported the package and created a session.

Querying objects

The old API relied on predefined methods for querying objects and constructors which enabled you to get an entity by it’s id or name.

Old API:

project = ftrack.getProject('dev_tutorial')
task = ftrack.Task('8923b7b3-4bf0-11e5-8811-3c0754289fd3')
user = ftrack.User('jane')

New API:

project = session.query('Project where name is "dev_tutorial"').one()
task = session.get('Task', '8923b7b3-4bf0-11e5-8811-3c0754289fd3')
user = session.query('User where username is "jane"').one()

While the new API can be a bit more verbose for simple queries, it is much more powerful and allows you to filter on any field and preload related data:

tasks = session.query(
    'select name, parent.name from Task '
    'where project.full_name is "My Project" '
    'and status.type.short is "DONE" '
    'and not timelogs any ()'
).all()

The above fetches all tasks for “My Project” that are done but have no timelogs. It also pre-fetches related information about the tasks parent – all in one efficient query.

See also

Querying

Creating objects

In the old API, you create objects using specialized methods, such as ftrack.createProject(), Project.createSequence() and Task.createShot().

In the new API, you can create any object using Session.create(). In addition, there are a few helper methods to reduce the amount of boilerplate necessary to create certain objects. Don’t forget to call Session.commit() once you have issued your create statements to commit your changes.

As an example, let’s look at populating a project with a few entities.

Old API:

project = ftrack.getProject('migration_test')

# Get default task type and status from project schema
taskType = project.getTaskTypes()[0]
taskStatus = project.getTaskStatuses(taskType)[0]

sequence = project.createSequence('001')

# Create five shots with one task each
for shot_number in xrange(10, 60, 10):
    shot = sequence.createShot(
        '{0:03d}'.format(shot_number)
    )
    shot.createTask(
        'Task name',
        taskType,
        taskStatus
    )

New API:

project = session.query('Project where name is "migration_test"').one()

# Get default task type and status from project schema
project_schema = project['project_schema']
default_shot_status = project_schema.get_statuses('Shot')[0]
default_task_type = project_schema.get_types('Task')[0]
default_task_status = project_schema.get_statuses(
    'Task', default_task_type['id']
)[0]

# Create sequence
sequence = session.create('Sequence', {
    'name': '001',
    'parent': project
})

# Create five shots with one task each
for shot_number in xrange(10, 60, 10):
    shot = session.create('Shot', {
        'name': '{0:03d}'.format(shot_number),
        'parent': sequence,
        'status': default_shot_status
    })
    session.create('Task', {
        'name': 'Task name',
        'parent': shot,
        'status': default_task_status,
        'type': default_task_type
    })

# Commit all changes to the server.
session.commit()

If you test the example above, one thing you might notice is that the new API is much more efficient. Thanks to the transaction-based architecture in the new API only a single call to the server is required to create all the objects.

Updating objects

Updating objects in the new API works in a similar way to the old API. Instead of using the set() method on objects, you simply set the key of the entity to the new value, and call Session.commit() to persist the changes to the database.

The following example adjusts the duration and comment of a timelog for a user using the old and new API, respectively.

Old API:

import ftrack

user = ftrack.User('john')
user.set('email', 'john@example.com')

New API:

import ftrack_api
session = ftrack_api.Session()

user = session.query('User where username is "john"').one()
user['email'] = 'john@example.com'
session.commit()

Date and datetime attributes

In the old API, date and datetime attributes where represented using a standard datetime object. In the new API we have opted to use the arrow library instead. Datetime attributes are represented in the server timezone, but with the timezone information stripped.

Old API:

>>> import datetime

>>> task_old_api = ftrack.Task(task_id)
>>> task_old_api.get('startdate')
datetime.datetime(2015, 9, 2, 0, 0)

>>> # Updating a datetime attribute
>>> task_old_api.set('startdate', datetime.date.today())

New API:

>>> import arrow

>>> task_new_api = session.get('Task', task_id)
>>> task_new_api['start_date']
<Arrow [2015-09-02T00:00:00+00:00]>

>>> # In the new API, utilize the arrow library when updating a datetime.
>>> task_new_api['start_date'] = arrow.utcnow().floor('day')
>>> session.commit()

Custom attributes

In the old API, custom attributes could be retrieved from an entity by using the methods get() and set(), like standard attributes. In the new API, custom attributes can be written and read from entities using the custom_attributes property, which provides a dictionary-like interface.

Old API:

>>> task_old_api = ftrack.Task(task_id)
>>> task_old_api.get('my_custom_attribute')

>>> task_old_api.set('my_custom_attribute', 'My new value')

New API:

>>> task_new_api = session.get('Task', task_id)
>>> task_new_api['custom_attributes']['my_custom_attribute']


>>> task_new_api['custom_attributes']['my_custom_attribute'] = 'My new value'

For more information on working with custom attributes and existing limitations, please see:

Using both APIs side-by-side

With so many powerful new features and the necessary support for more flexible workflows, we chose early on to not limit the new API design by necessitating backwards compatibility. However, we also didn’t want to force teams using the existing API to make a costly all-or-nothing switchover. As such, we have made the new API capable of coexisting in the same process as the old API:

import ftrack
import ftrack_api

In addition, the old API will continue to be supported for some time, but do note that it will not support the new Workflows and will not have new features back ported to it.

In the first example, we obtain a task reference using the old API and then use the new API to assign a user to it:

import ftrack
import ftrack_api

# Create session for new API, authenticating using envvars.
session = ftrack_api.Session()

# Obtain task id using old API
shot = ftrack.getShot(['migration_test', '001', '010'])
task = shot.getTasks()[0]
task_id = task.getId()

user = session.query(
    'User where username is "{0}"'.format(session.api_user)
).one()
session.create('Appointment', {
    'resource': user,
    'context_id': task_id,
    'type': 'assignment'
})

The second example fetches a version using the new API and uploads and sets a thumbnail using the old API:

import arrow
import ftrack

# fetch a version published today
version = session.query(
    'AssetVersion where date >= "{0}"'.format(
        arrow.now().floor('day')
    )
).first()

# Create a thumbnail using the old api.
thumbnail_path = '/path/to/thumbnail.jpg'
version_old_api = ftrack.AssetVersion(version['id'])
thumbnail = version_old_api.createThumbnail(thumbnail_path)

# Also set the same thumbnail on the task linked to the version.
task_old_api = ftrack.Task(version['task_id'])
task_old_api.setThumbnail(thumbnail)

Note

It is now possible to set thumbnails using the new API as well, for more info see Working with thumbnails.

Plugin registration

To make event and location plugin register functions work with both old and new API the function should be updated to validate the input arguments. For old plugins the register method should validate that the first input is of type ftrack.Registry, and for the new API it should be of type ftrack_api.session.Session.

If the input parameter is not validated, a plugin might be mistakenly registered twice, since both the new and old API will look for plugins the same directories.

Example: publishing a new version

In the following example, we look at migrating a script which publishes a new version with two components.

Old API:

# Query a shot and a task to create the asset against.
shot = ftrack.getShot(['dev_tutorial', '001', '010'])
task = shot.getTasks()[0]

# Create new asset.
asset = shot.createAsset(name='forest', assetType='geo')

# Create a new version for the asset.
version = asset.createVersion(
    comment='Added more leaves.',
    taskid=task.getId()
)

# Get the calculated version number.
print version.getVersion()

# Add some components.
previewPath = '/path/to/forest_preview.mov'
previewComponent = version.createComponent(path=previewPath)

modelPath = '/path/to/forest_mode.ma'
modelComponent = version.createComponent(name='model', path=modelPath)

# Publish.
asset.publish()

# Add thumbnail to version.
thumbnail = version.createThumbnail('/path/to/forest_thumbnail.jpg')

# Set thumbnail on other objects without duplicating it.
task.setThumbnail(thumbnail)

New API:

# Query a shot and a task to create the asset against.
shot = session.query(
    'Shot where project.name is "dev_tutorial" '
    'and parent.name is "001" and name is "010"'
).one()
task = shot['children'][0]

# Create new asset.
asset_type = session.query('AssetType where short is "geo"').first()
asset = session.create('Asset', {
    'parent': shot,
    'name': 'forest',
    'type': asset_type
})

# Create a new version for the asset.
status = session.query('Status where name is "Pending"').one()
version = session.create('AssetVersion', {
    'asset': asset,
    'status': status,
    'comment': 'Added more leaves.',
    'task': task
})

# In the new API, the version number is not set until we persist the changes
print 'Version number before commit: {0}'.format(version['version'])
session.commit()
print 'Version number after commit: {0}'.format(version['version'])

# Add some components.
preview_path = '/path/to/forest_preview.mov'
preview_component = version.create_component(preview_path, location='auto')

model_path = '/path/to/forest_mode.ma'
model_component = version.create_component(model_path, {
    'name': 'model'
}, location='auto')

# Publish. Newly created version defaults to being published in the new api,
# but if set to false you can update it by setting the key on the version.
version['is_published'] = True

# Persist the changes
session.commit()

# Add thumbnail to version.
thumbnail = version.create_thumbnail(
    '/path/to/forest_thumbnail.jpg'
)

# Set thumbnail on other objects without duplicating it.
task['thumbnail'] = thumbnail
session.commit()

Workarounds for missing convenience methods

Query object by path

In the old API, there existed a convenience methods to get an object by referencing the path (i.e object and parent names).

Old API:

shot = ftrack.getShot(['dev_tutorial', '001', '010'])

New API:

shot = session.query(
    'Shot where project.name is "dev_tutorial" '
    'and parent.name is "001" and name is "010"'
)
Retrieving an object’s parents

To retrieve a list of an object’s parents, you could call the method getParents() in the old API. Currently, it is not possible to fetch this in a single call using the new API, so you will have to traverse the ancestors one-by-one and fetch each object’s parent.

Old API:

parents = task.getParents()

New API:

parents = []
for item in task['link'][:-1]:
    parents.append(session.get(item['type'], item['id']))

Note that link includes the task itself so [:-1] is used to only retreive the parents. To learn more about the link attribute, see Using link attributes example.

Limitations in the current version of the API

The new API is still quite young and in active development and there are a few limitations currently to keep in mind when using it.

Missing schemas

The following entities are as of the time of writing not currently available in the new API. Let us know if you depend on any of them.

  • Booking
  • Calendar and Calendar Type
  • Dependency
  • Manager and Manager Type
  • Phase
  • Role
  • Task template
  • Temp data
Action base class

There is currently no helper class for creating actions using the new API. We will add one in the near future.

In the meantime, it is still possible to create actions without the base class by listening and responding to the ftrack.action.discover and ftrack.action.launch events.

Legacy location

The ftrack legacy disk locations utilizing the InternalResourceIdentifierTransformer has been deprecated.

Glossary

accessor

An implementation (typically a Python plugin) for accessing a particular type of storage using a specific protocol.

See also

Accessors

action

Actions in ftrack provide a standardised way to integrate other tools, either off-the-shelf or custom built, directly into your ftrack workflow.

api
Application programming interface.
arrow
A Python library that offers a sensible, human-friendly approach to creating, manipulating, formatting and converting dates, times, and timestamps. Read more at http://crsmithdev.com/arrow/
asset
A container for asset versions, typically representing the output from an artist. For example, ‘geometry’ from a modeling artist. Has an asset type that categorises the asset.
asset type
Category for a particular asset.
asset version
A specific version of data for an asset. Can contain multiple components.
component
A container to hold any type of data (such as a file or file sequence). An asset version can have any number of components, each with a specific name. For example, a published version of geometry might have two components containing the high and low resolution files, with the component names as ‘hires’ and ‘lowres’ respectively.
PEP-8
Style guide for Python code. Read the guide at https://www.python.org/dev/peps/pep-0008/
plugin

Python plugins are used by the API to extend it with new functionality, such as locations or actions.

python
A programming language that lets you work more quickly and integrate your systems more effectively. Often used in creative industries. Visit the language website at http://www.python.org
PyPi
Python package index. The Python Package Index or PyPI is the official third-party software repository for the Python programming language. Visit the website at https://pypi.python.org/pypi
resource identifier

A string that is stored in ftrack as a reference to a resource (such as a file) in a specific location. Used by accessors to determine how to access data.

Indices and tables