diff --git a/doc/example/workflow_schema.rst b/doc/example/workflow_schema.rst index a601d007..373ec78b 100644 --- a/doc/example/workflow_schema.rst +++ b/doc/example/workflow_schema.rst @@ -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() + + diff --git a/doc/release/release_notes.rst b/doc/release/release_notes.rst index 2af6b8c7..6a5ee5fc 100644 --- a/doc/release/release_notes.rst +++ b/doc/release/release_notes.rst @@ -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 diff --git a/source/ftrack_api/plugin.py b/source/ftrack_api/plugin.py index 8c164edf..ab114268 100644 --- a/source/ftrack_api/plugin.py +++ b/source/ftrack_api/plugin.py @@ -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*. @@ -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: diff --git a/test/unit/test_plugin.py b/test/unit/test_plugin.py index 90d6339c..8a8771bb 100644 --- a/test/unit/test_plugin.py +++ b/test/unit/test_plugin.py @@ -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(): @@ -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(