Skip to content

Commit

Permalink
Merge branch 'master' into backlog/review-compound-extension-tests
Browse files Browse the repository at this point in the history
  • Loading branch information
hdd committed Nov 9, 2020
2 parents 97e604e + 099d445 commit 1ac65b8
Show file tree
Hide file tree
Showing 4 changed files with 151 additions and 98 deletions.
229 changes: 134 additions & 95 deletions doc/example/workflow_schema.rst
Original file line number Diff line number Diff line change
Expand Up @@ -21,116 +21,155 @@ project.

ftrack server version 4.5 or higher is required for this example to work.

Below is an example of how to create a complete `ProjectSchema`::

# Start by querying the data we need to create our project schema.
asset_build = session.query(
'select id from ObjectType where name is "Asset build"'
).first()
milestone = session.query(
'select id from ObjectType where name is "Milestone"'
).first()
status_1 = session.query(
'select id from Status where name is "Not started"'
).first()
status_2 = session.query(
'select id from Status where name is "In progress"'
).first()
type_1 = session.query(
'select id from Type where name is "Modeling"'
).first()
type_2 = session.query(
'select id from Type where name is "Animation"'
).first()

# Task and AssetVersion objects use WorkflowSchema to define what statuses they
# can have. Each WorkflowSchema is then linked to one or multiple statuses using
# a WorkflowSchemaStatus object.
task_workflow_schema = session.create('WorkflowSchema')
version_workflow_schema = session.create('WorkflowSchema')
session.create('WorkflowSchemaStatus', {
'workflow_schema_id': task_workflow_schema['id'],
'status_id': status_1['id']
})
session.create('WorkflowSchemaStatus', {
'workflow_schema_id': version_workflow_schema['id'],
'status_id': status_1['id']
})

# Tasks can also have different types and those are linked to the task using a
# TaskTypeSchema together with one or multiple TaskTypeSchemaType.
task_type_schema = session.create('TaskTypeSchema')
session.create('TaskTypeSchemaType', {
'task_type_schema_id': task_type_schema['id'],
'type_id': type_1['id']
})
Below is an example of how to create a complete `ProjectSchema`


Start by querying the data we need to create our project schema::

status_not_started = session.query('Status where name="{}"'.format('Not started')).one()
status_in_progress = session.query('Status where name="{}"'.format('In progress')).one()
status_completed = session.query('Status where name="{}"'.format('Completed')).one()
status_on_hold = session.query('Status where name="{}"'.format('On Hold')).one()

all_statuses = [status_not_started, status_in_progress, status_completed, status_on_hold]

type_generic = session.query('Type where name="{}"'.format('Generic')).one()
type_modeling = session.query('Type where name="{}"'.format('Modeling')).one()
type_rigging = session.query('Type where name="{}"'.format('Rigging')).one()
type_compositing = session.query('Type where name="{}"'.format('Compositing')).one()
type_deliverable = session.query('Type where name="{}"'.format('Deliverable')).one()
type_character = session.query('Type where name="{}"'.format('Character')).one()

all_types = [type_generic, type_modeling, type_rigging, type_compositing, type_deliverable, type_character]

Create a WorkflowSchema defining statuses tasks can have. Each WorkflowSchema is then linked to one or multiple
statuses using a WorkflowSchemaStatus object::

workflow_schema = session.create('WorkflowSchema')

for status in all_statuses:
session.create('WorkflowSchemaStatus', {
'workflow_schema_id': workflow_schema['id'],
'status_id': status['id']
})




Define task types by creating a TaskTypeSchema, linking it to types through TaskTypeSchemaType::

task_schema = session.create('TaskTypeSchema')

for typ in all_types:
session.create('TaskTypeSchemaType', {
'task_type_schema_id': task_schema['id'],
'type_id': typ['id']
})

In a similar manner, define which states a version can have by creating another WorkflowSchema::

version_schema = session.create('WorkflowSchema')

for status in all_statuses:
session.create('WorkflowSchemaStatus', {
'workflow_schema_id': version_schema['id'],
'status_id': status['id']
})


Then we create the ProjectSchema itself with the ids of the workflows and schemas we already created for Task and
AssetVersion::

# Then we create the ProjectSchema itself with the ids of the workflows and
# schemas we already created for Task and AssetVersion.
project_schema = session.create('ProjectSchema', {
'name': 'My custom schema',
'asset_version_workflow_schema_id': version_workflow_schema['id'],
'task_workflow_schema_id': task_workflow_schema['id'],
'task_type_schema_id': task_type_schema['id']
'task_workflow_schema_id': workflow_schema['id'],
'task_type_schema_id': task_schema['id'],
'asset_version_workflow_schema_id': version_schema['id'],
})

# Then link any additional object types to the ProjectSchema using
# ProjectSchemaObjectType.
session.create('ProjectSchemaObjectType', {
'project_schema_id': project_schema['id'],
'object_type_id': asset_build['id']
})

# Each ObjectType can have statuses and types by creating a Schema and the
# corresponding SchemaType and SchemaStatus.
asset_build_schema = session.create('Schema', {
'project_schema_id': project_schema['id'],
'object_type_id': asset_build['id']
})
session.create('SchemaType', {
'schema_id': asset_build_schema['id'],
'type_id': type_1['id']
})
session.create('SchemaStatus', {
'schema_id': asset_build_schema['id'],
'status_id': status_1['id']
Define objects contained in schema, linked with ProjectSchemaObjectType. Also define which statuses and types each
object type can have, linked with Schema and SchemaStatus & SchemaType.

In this example we limit Asset Builds to a reduced set of statuses and types.

Milestone is a built in type which will always be part of a ProjectSchema but it also needs to have statuses and types::

objecttype_sequence = session.query('ObjectType where name="{}"'.format('Sequence')).one()
objecttype_shot = session.query('ObjectType where name="{}"'.format('Shot')).one()
objecttype_folder = session.query('ObjectType where name="{}"'.format('Folder')).one()
objecttype_assetbuild = session.query('ObjectType where name="{}"'.format('Asset Build')).one()
objecttype_milestone = session.query('ObjectType where name="{}"'.format('Milestone')).one()

for (objecttype, statuses, types) in [
(objecttype_sequence, None, None),
(objecttype_shot, None, None),
(objecttype_folder, None, None),
(objecttype_assetbuild,
[status_in_progress,status_completed,status_on_hold],
[type_generic, type_character]),
(objecttype_milestone, None, None),
]:
session.create('ProjectSchemaObjectType', {
'project_schema_id': project_schema['id'],
'object_type_id': objecttype['id']
})

object_type_schema = session.create('Schema', {
'project_schema_id': project_schema['id'],
'object_type_id': objecttype['id']
})

for status in statuses or all_statuses:
session.create('SchemaStatus', {
'schema_id': object_type_schema['id'],
'status_id': status['id']
})
for typ in types or all_types:
session.create('SchemaType', {
'schema_id': object_type_schema['id'],
'type_id': typ['id']
})

A more complex `ProjectSchema` can have overrides for Task where different types of tasks can have different statuses.
For example Animation can have different statuses compared to all other types such as Modelling.

Create a override for Deliverable task type, allowing it to only have two states::

override_workflow_schema = session.create('WorkflowSchema', {
'name': 'My schema deliverable override '
})
for status in [status_in_progress, status_completed]:
session.create('WorkflowSchemaStatus', {
'workflow_schema_id': override_workflow_schema['id'],
'status_id': status['id']
})

# Milestone is a built in type which will always be part of a ProjectSchema but
# it also needs to have statuses and types.
milestone_schema = session.create('Schema', {
session.create('ProjectSchemaOverride', {
'project_schema_id': project_schema['id'],
'object_type_id': milestone['id']
})
session.create('SchemaType', {
'schema_id': milestone_schema['id'],
'type_id': type_1['id']
'type_id': type_deliverable['id'],
'workflow_schema_id': override_workflow_schema['id'],
})
session.create('SchemaStatus', {
'schema_id': milestone_schema['id'],
'status_id': status_1['id']
})

session.commit()

A more complex `ProjectSchema` can have overrides for Task where different types
of tasks can have different statuses.

Below is an example of how to add an override::
A task template can be used when creating Asset builds, Shots, etc to also have a predefined set of tasks created.
Create a Modeling template containing two task types::

# Task can have overrides which define different statuses for a type so that
# Animation can have different statuses compared to all other types such as
# Modelling.
task_override_workflow_schema = session.create('WorkflowSchema')
session.create('WorkflowSchemaStatus', {
'workflow_schema_id': task_override_workflow_schema['id'],
'status_id': status_2['id']
})
session.create('ProjectSchemaOverride', {
'id': str(uuid.uuid4()),
task_template = session.create('TaskTemplate', {
'project_schema_id': project_schema['id'],
'workflow_schema_id': task_override_workflow_schema['id'],
'type_id': type_2['id']
'name': 'Modeling'
})
for typ in [type_modeling, type_rigging]:
session.create('TaskTemplateItem', {
'template_id': task_template['id'],
'task_type_id': typ['id']
})



Finally, commit our new project schema::

session.commit()


10 changes: 10 additions & 0 deletions doc/release/release_notes.rst
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,16 @@ Release Notes

.. release:: Upcoming

.. change:: changed
:tags: logging

:meth:`ftrack_api.plugin.discover` now logs import error traceback.

.. change:: changed
:tags: Documentation

Updated workflow schema creation example with more readable statues and types. Added task template.

.. change:: new
:tags: session, events

Expand Down
4 changes: 3 additions & 1 deletion source/ftrack_api/plugin.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
import uuid
import imp
import inspect

import traceback

def discover(paths, positional_arguments=None, keyword_arguments=None):
'''Find and load plugins in search *paths*.
Expand Down Expand Up @@ -51,6 +51,8 @@ def discover(paths, positional_arguments=None, keyword_arguments=None):
'Failed to load plugin from "{0}": {1}'
.format(module_path, error)
)
logger.debug(
traceback.format_exc())
continue

try:
Expand Down
6 changes: 4 additions & 2 deletions test/unit/test_plugin.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ def python_non_plugin(temporary_path):
with open(os.path.join(temporary_path, 'non.py'), 'w') as file_object:
file_object.write(textwrap.dedent('''
from __future__ import print_function
print("Not a plugin")
def not_called():
Expand Down Expand Up @@ -117,9 +117,11 @@ def test_discover_broken_plugin(broken_plugin, caplog):

else:
records = caplog.records
assert len(records) == 1
assert len(records) == 2
assert records[0].levelno is logging.WARNING
assert records[1].levelno is logging.DEBUG
assert 'Failed to load plugin' in records[0].message
assert 'Traceback' in records[1].message


@pytest.mark.parametrize(
Expand Down

0 comments on commit 1ac65b8

Please sign in to comment.