diff --git a/pype/ftrack/actions/action_delete_asset.py b/pype/ftrack/actions/action_delete_asset.py index fc9e66e4f8..6a3b724379 100644 --- a/pype/ftrack/actions/action_delete_asset.py +++ b/pype/ftrack/actions/action_delete_asset.py @@ -49,27 +49,23 @@ class DeleteAssetSubset(BaseAction): def _launch(self, event): try: - args = self._translate_event( - self.session, event - ) + entities = self._translate_event(event) if "values" not in event["data"]: self.dbcon.install() - return self._interface(self.session, *args) + return self._interface(self.session, entities, event) - confirmation = self.confirm_delete(*args) + confirmation = self.confirm_delete(entities, event) if confirmation: return confirmation self.dbcon.install() response = self.launch( - self.session, *args + self.session, entities, event ) finally: self.dbcon.uninstall() - return self._handle_result( - self.session, response, *args - ) + return self._handle_result(response) def interface(self, session, entities, event): self.show_message(event, "Preparing data...", True) diff --git a/pype/ftrack/actions/action_store_thumbnails_to_avalon.py b/pype/ftrack/actions/action_store_thumbnails_to_avalon.py index 7adc36f4b5..051156c2f8 100644 --- a/pype/ftrack/actions/action_store_thumbnails_to_avalon.py +++ b/pype/ftrack/actions/action_store_thumbnails_to_avalon.py @@ -5,13 +5,11 @@ import json from bson.objectid import ObjectId from pype.ftrack import BaseAction -from pype.ftrack.lib import ( - get_project_from_entity, - get_avalon_entities_for_assetversion -) from pypeapp import Anatomy from pype.ftrack.lib.io_nonsingleton import DbConnector +from pype.ftrack.lib.avalon_sync import CustAttrIdKey + class StoreThumbnailsToAvalon(BaseAction): # Action identifier @@ -89,7 +87,7 @@ class StoreThumbnailsToAvalon(BaseAction): "message": msg } - project = get_project_from_entity(entities[0]) + project = self.get_project_from_entity(entities[0]) project_name = project["full_name"] anatomy = Anatomy(project_name) @@ -186,7 +184,7 @@ class StoreThumbnailsToAvalon(BaseAction): ).format(entity["id"])) continue - avalon_ents_result = get_avalon_entities_for_assetversion( + avalon_ents_result = self.get_avalon_entities_for_assetversion( entity, self.db_con ) version_full_path = ( @@ -345,6 +343,119 @@ class StoreThumbnailsToAvalon(BaseAction): file_open.close() return True + def get_avalon_entities_for_assetversion(self, asset_version, db_con): + output = { + "success": True, + "message": None, + "project": None, + "project_name": None, + "asset": None, + "asset_name": None, + "asset_path": None, + "subset": None, + "subset_name": None, + "version": None, + "version_name": None, + "representations": None + } + + db_con.install() + + ft_asset = asset_version["asset"] + subset_name = ft_asset["name"] + version = asset_version["version"] + parent = ft_asset["parent"] + ent_path = "/".join( + [ent["name"] for ent in parent["link"]] + ) + project = self.get_project_from_entity(asset_version) + project_name = project["full_name"] + + output["project_name"] = project_name + output["asset_name"] = parent["name"] + output["asset_path"] = ent_path + output["subset_name"] = subset_name + output["version_name"] = version + + db_con.Session["AVALON_PROJECT"] = project_name + + avalon_project = db_con.find_one({"type": "project"}) + output["project"] = avalon_project + + if not avalon_project: + output["success"] = False + output["message"] = ( + "Project not synchronized to avalon `{}`".format(project_name) + ) + return output + + asset_ent = None + asset_mongo_id = parent["custom_attributes"].get(CustAttrIdKey) + if asset_mongo_id: + try: + asset_mongo_id = ObjectId(asset_mongo_id) + asset_ent = db_con.find_one({ + "type": "asset", + "_id": asset_mongo_id + }) + except Exception: + pass + + if not asset_ent: + asset_ent = db_con.find_one({ + "type": "asset", + "data.ftrackId": parent["id"] + }) + + output["asset"] = asset_ent + + if not asset_ent: + output["success"] = False + output["message"] = ( + "Not synchronized entity to avalon `{}`".format(ent_path) + ) + return output + + asset_mongo_id = asset_ent["_id"] + + subset_ent = db_con.find_one({ + "type": "subset", + "parent": asset_mongo_id, + "name": subset_name + }) + + output["subset"] = subset_ent + + if not subset_ent: + output["success"] = False + output["message"] = ( + "Subset `{}` does not exist under Asset `{}`" + ).format(subset_name, ent_path) + return output + + version_ent = db_con.find_one({ + "type": "version", + "name": version, + "parent": subset_ent["_id"] + }) + + output["version"] = version_ent + + if not version_ent: + output["success"] = False + output["message"] = ( + "Version `{}` does not exist under Subset `{}` | Asset `{}`" + ).format(version, subset_name, ent_path) + return output + + repre_ents = list(db_con.find({ + "type": "representation", + "parent": version_ent["_id"] + })) + + output["representations"] = repre_ents + return output + def register(session, plugins_presets={}): StoreThumbnailsToAvalon(session, plugins_presets).register() diff --git a/pype/ftrack/lib/__init__.py b/pype/ftrack/lib/__init__.py index 9da3b819b3..e58804440a 100644 --- a/pype/ftrack/lib/__init__.py +++ b/pype/ftrack/lib/__init__.py @@ -1,11 +1,15 @@ from . import avalon_sync from . import credentials -from .ftrack_app_handler import * -from .ftrack_event_handler import * -from .ftrack_action_handler import * -from .ftrack_base_handler import * +from .ftrack_base_handler import BaseHandler +from .ftrack_event_handler import BaseEvent +from .ftrack_action_handler import BaseAction +from .ftrack_app_handler import AppAction -from .lib import ( - get_project_from_entity, - get_avalon_entities_for_assetversion -) +__all__ = [ + "avalon_sync", + "credentials", + "BaseHandler", + "BaseEvent", + "BaseAction", + "AppAction" +] diff --git a/pype/ftrack/lib/ftrack_action_handler.py b/pype/ftrack/lib/ftrack_action_handler.py index 7fd7eccfb7..66d321316f 100644 --- a/pype/ftrack/lib/ftrack_action_handler.py +++ b/pype/ftrack/lib/ftrack_action_handler.py @@ -23,17 +23,13 @@ class BaseAction(BaseHandler): def __init__(self, session, plugins_presets={}): '''Expects a ftrack_api.Session instance''' - super().__init__(session, plugins_presets) - if self.label is None: - raise ValueError( - 'Action missing label.' - ) + raise ValueError('Action missing label.') - elif self.identifier is None: - raise ValueError( - 'Action missing identifier.' - ) + if self.identifier is None: + raise ValueError('Action missing identifier.') + + super().__init__(session, plugins_presets) def register(self): ''' @@ -61,66 +57,131 @@ class BaseAction(BaseHandler): self._launch ) - def _launch(self, event): - args = self._translate_event( - self.session, event + def _discover(self, event): + entities = self._translate_event(event) + accepts = self.discover(self.session, entities, event) + if not accepts: + return + + self.log.debug(u'Discovering action with selection: {0}'.format( + event['data'].get('selection', []) + )) + + return { + 'items': [{ + 'label': self.label, + 'variant': self.variant, + 'description': self.description, + 'actionIdentifier': self.identifier, + 'icon': self.icon, + }] + } + + def discover(self, session, entities, event): + '''Return true if we can handle the selected entities. + + *session* is a `ftrack_api.Session` instance + + + *entities* is a list of tuples each containing the entity type and the + entity id. If the entity is a hierarchical you will always get the + entity type TypedContext, once retrieved through a get operation you + will have the "real" entity type ie. example Shot, Sequence + or Asset Build. + + *event* the unmodified original event + + ''' + + return False + + def _interface(self, session, entities, event): + interface = self.interface(session, entities, event) + if not interface: + return + + if isinstance(interface, (tuple, list)): + return {"items": interface} + + if isinstance(interface, dict): + if ( + "items" in interface + or ("success" in interface and "message" in interface) + ): + return interface + + raise ValueError(( + "Invalid interface output expected key: \"items\" or keys:" + " \"success\" and \"message\". Got: \"{}\"" + ).format(str(interface))) + + raise ValueError( + "Invalid interface output type \"{}\"".format( + str(type(interface)) + ) ) + def interface(self, session, entities, event): + '''Return a interface if applicable or None + + *session* is a `ftrack_api.Session` instance + + *entities* is a list of tuples each containing the entity type and + the entity id. If the entity is a hierarchical you will always get the + entity type TypedContext, once retrieved through a get operation you + will have the "real" entity type ie. example Shot, Sequence + or Asset Build. + + *event* the unmodified original event + ''' + return None + + def _launch(self, event): + entities = self._translate_event(event) + preactions_launched = self._handle_preactions(self.session, event) if preactions_launched is False: return interface = self._interface( - self.session, *args + self.session, entities, event ) if interface: return interface response = self.launch( - self.session, *args + self.session, entities, event ) - return self._handle_result( - self.session, response, *args - ) + return self._handle_result(response) - def _handle_result(self, session, result, entities, event): + def _handle_result(self, result): '''Validate the returned result from the action callback''' if isinstance(result, bool): if result is True: - result = { - 'success': result, - 'message': ( - '{0} launched successfully.'.format(self.label) - ) - } + msg = 'Action {0} finished.' else: - result = { - 'success': result, - 'message': ( - '{0} launch failed.'.format(self.label) - ) - } + msg = 'Action {0} failed.' - elif isinstance(result, dict): + return { + 'success': result, + 'message': msg.format(self.label) + } + + if isinstance(result, dict): if 'items' in result: - items = result['items'] - if not isinstance(items, list): + if not isinstance(result['items'], list): raise ValueError('Invalid items format, must be list!') else: for key in ('success', 'message'): - if key in result: - continue + if key not in result: + raise KeyError('Missing required key: {0}.'.format(key)) + return result - raise KeyError( - 'Missing required key: {0}.'.format(key) - ) - - else: - self.log.error( - 'Invalid result type must be bool or dictionary!' - ) + self.log.warning(( + 'Invalid result type \"{}\" must be bool or dictionary!' + ).format(str(type(result)))) return result diff --git a/pype/ftrack/lib/ftrack_app_handler.py b/pype/ftrack/lib/ftrack_app_handler.py index 56196d15f9..53a52b1ff9 100644 --- a/pype/ftrack/lib/ftrack_app_handler.py +++ b/pype/ftrack/lib/ftrack_app_handler.py @@ -1,44 +1,35 @@ import os import sys import platform -from avalon import lib as avalonlib +import avalon.lib import acre -from pype import api as pype from pype import lib as pypelib from pypeapp import config -from .ftrack_base_handler import BaseHandler +from .ftrack_action_handler import BaseAction from pypeapp import Anatomy -class AppAction(BaseHandler): - '''Custom Action base class +class AppAction(BaseAction): + """Application Action class. -