From de4804f4143c75411ca8267521305f17d2c99ed9 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Mon, 21 Jan 2019 10:29:11 +0100 Subject: [PATCH 01/27] _clear_credentials also removes environments so logout actually works --- pype/ftrack/credentials.py | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/pype/ftrack/credentials.py b/pype/ftrack/credentials.py index 3efad41997..6f756e8a52 100644 --- a/pype/ftrack/credentials.py +++ b/pype/ftrack/credentials.py @@ -4,7 +4,8 @@ import toml import ftrack_api import appdirs -config_path = os.path.normpath(appdirs.user_data_dir('pype-app','pype')) + +config_path = os.path.normpath(appdirs.user_data_dir('pype-app', 'pype')) config_name = 'ftrack_cred.toml' fpath = os.path.join(config_path, config_name) folder = os.path.dirname(fpath) @@ -12,6 +13,7 @@ folder = os.path.dirname(fpath) if not os.path.isdir(folder): os.makedirs(folder) + def _get_credentials(): folder = os.path.dirname(fpath) @@ -21,7 +23,7 @@ def _get_credentials(): try: file = open(fpath, 'r') - except: + except Exception: filecreate = open(fpath, 'w') filecreate.close() file = open(fpath, 'r') @@ -31,25 +33,30 @@ def _get_credentials(): return credentials + def _save_credentials(username, apiKey): file = open(fpath, 'w') data = { - 'username':username, - 'apiKey':apiKey + 'username': username, + 'apiKey': apiKey } credentials = toml.dumps(data) file.write(credentials) file.close() + def _clear_credentials(): file = open(fpath, 'w').close() + _set_env(None, None) + def _set_env(username, apiKey): os.environ['FTRACK_API_USER'] = username os.environ['FTRACK_API_KEY'] = apiKey + def _check_credentials(username=None, apiKey=None): if username and apiKey: From e49e50fbd897180c7e13885ec39fe7aafe2fbcdd Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Mon, 21 Jan 2019 11:25:39 +0100 Subject: [PATCH 02/27] shortened time for register apps --- pype/ftrack/actions/action_Apps.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pype/ftrack/actions/action_Apps.py b/pype/ftrack/actions/action_Apps.py index fe7664470a..dabba18a04 100644 --- a/pype/ftrack/actions/action_Apps.py +++ b/pype/ftrack/actions/action_Apps.py @@ -1,6 +1,6 @@ import toml import time -from ftrack_action_handler import AppAction +from pype.ftrack import AppAction from avalon import lib from app.api import Logger from pype import lib as pypelib @@ -59,6 +59,6 @@ def register(session): for app in apps: try: registerApp(app, session) - time.sleep(0.05) + time.sleep(0.03) except Exception as e: log.warning("'{0}' - not proper App ({1})".format(app['name'], e)) From f9004498972c7afc816dd83db30098038e3c5ec5 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Mon, 21 Jan 2019 12:56:03 +0100 Subject: [PATCH 03/27] flake8 formatting --- pype/ftrack/actions/action_asset_delete.py | 23 +++-- .../actions/action_client_review_sort.py | 10 +-- pype/ftrack/actions/action_component_open.py | 15 ++-- .../actions/action_create_cust_attrs.py | 12 ++- .../actions/action_delete_unpublished.py | 7 +- pype/ftrack/actions/action_set_version.py | 30 +++---- .../actions/action_sync_to_avalon_local.py | 9 +- pype/ftrack/actions/action_test.py | 2 +- pype/ftrack/actions/action_thumbToChildern.py | 15 ++-- pype/ftrack/actions/action_thumbToParent.py | 27 +++--- pype/ftrack/actions/djvview.py | 86 +++++++++++++------ pype/ftrack/event_server.py | 8 +- pype/ftrack/login_dialog.py | 48 +++++++---- 13 files changed, 171 insertions(+), 121 deletions(-) diff --git a/pype/ftrack/actions/action_asset_delete.py b/pype/ftrack/actions/action_asset_delete.py index 5c91acfff7..2359c2e499 100644 --- a/pype/ftrack/actions/action_asset_delete.py +++ b/pype/ftrack/actions/action_asset_delete.py @@ -1,9 +1,8 @@ import sys import argparse import logging -import getpass import ftrack_api -from ftrack_action_handler import BaseAction +from pype.ftrack import BaseAction class AssetDelete(BaseAction): @@ -14,17 +13,17 @@ class AssetDelete(BaseAction): #: Action label. label = 'Asset Delete' - def discover(self, session, entities, event): ''' Validation ''' - - if (len(entities) != 1 or entities[0].entity_type - not in ['Shot', 'Asset Build']): + + if ( + len(entities) != 1 or + entities[0].entity_type not in ['Shot', 'Asset Build'] + ): return False return True - def interface(self, session, entities, event): if not event['data'].get('values', {}): @@ -38,10 +37,10 @@ class AssetDelete(BaseAction): label = asset['name'] items.append({ - 'label':label, - 'name':label, - 'value':False, - 'type':'boolean' + 'label': label, + 'name': label, + 'value': False, + 'type': 'boolean' }) if len(items) < 1: @@ -69,7 +68,7 @@ class AssetDelete(BaseAction): session.delete(asset) try: session.commit() - except: + except Exception: session.rollback() raise diff --git a/pype/ftrack/actions/action_client_review_sort.py b/pype/ftrack/actions/action_client_review_sort.py index f903239962..1e2f37ec74 100644 --- a/pype/ftrack/actions/action_client_review_sort.py +++ b/pype/ftrack/actions/action_client_review_sort.py @@ -1,11 +1,9 @@ import sys import argparse import logging -import os -import getpass import ftrack_api -from ftrack_action_handler import BaseAction +from pype.ftrack import BaseAction class ClientReviewSort(BaseAction): @@ -17,7 +15,6 @@ class ClientReviewSort(BaseAction): #: Action label. label = 'Sort Review' - def discover(self, session, entities, event): ''' Validation ''' @@ -26,7 +23,6 @@ class ClientReviewSort(BaseAction): return True - def launch(self, session, entities, event): entity = entities[0] @@ -40,7 +36,9 @@ class ClientReviewSort(BaseAction): # Sort criteria obj_list = sorted(obj_list, key=lambda k: k['version']) - obj_list = sorted(obj_list, key=lambda k: k['asset_version']['task']['name']) + obj_list = sorted( + obj_list, key=lambda k: k['asset_version']['task']['name'] + ) obj_list = sorted(obj_list, key=lambda k: k['name']) # Set 'sort order' to sorted list, so they are sorted in Ftrack also diff --git a/pype/ftrack/actions/action_component_open.py b/pype/ftrack/actions/action_component_open.py index 34ee752f61..e3a974156a 100644 --- a/pype/ftrack/actions/action_component_open.py +++ b/pype/ftrack/actions/action_component_open.py @@ -4,11 +4,10 @@ import sys import argparse import logging -import getpass import subprocess import os import ftrack_api -from ftrack_action_handler import BaseAction +from pype.ftrack import BaseAction class ComponentOpen(BaseAction): @@ -19,8 +18,10 @@ class ComponentOpen(BaseAction): # Action label label = 'Open File' # Action icon - icon = 'https://cdn4.iconfinder.com/data/icons/rcons-application/32/application_go_run-256.png', - + icon = ( + 'https://cdn4.iconfinder.com/data/icons/rcons-application/32/' + 'application_go_run-256.png' + ) def discover(self, session, entities, event): ''' Validation ''' @@ -29,13 +30,13 @@ class ComponentOpen(BaseAction): return True - def launch(self, session, entities, event): entity = entities[0] # Return error if component is on ftrack server - if entity['component_locations'][0]['location']['name'] == 'ftrack.server': + location_name = entity['component_locations'][0]['location']['name'] + if location_name == 'ftrack.server': return { 'success': False, 'message': "This component is stored on ftrack server!" @@ -49,7 +50,7 @@ class ComponentOpen(BaseAction): fpath = os.sep.join(items) if os.path.isdir(fpath): - if 'win' in sys.platform: # windows + if 'win' in sys.platform: # windows subprocess.Popen('explorer "%s"' % fpath) elif sys.platform == 'darwin': # macOS subprocess.Popen(['open', fpath]) diff --git a/pype/ftrack/actions/action_create_cust_attrs.py b/pype/ftrack/actions/action_create_cust_attrs.py index 3aa63e259b..ce3a6e7f84 100644 --- a/pype/ftrack/actions/action_create_cust_attrs.py +++ b/pype/ftrack/actions/action_create_cust_attrs.py @@ -6,7 +6,7 @@ import argparse import json import ftrack_api import arrow -from ftrack_action_handler import BaseAction +from pype.ftrack import BaseAction """ This action creates/updates custom attributes. @@ -117,15 +117,19 @@ class CustomAttributes(BaseAction): super().__init__(session) templates = os.environ['PYPE_STUDIO_TEMPLATES'] - path_items = [templates, 'presets', 'ftrack', 'ftrack_custom_attributes.json'] + path_items = [ + templates, 'presets', 'ftrack', 'ftrack_custom_attributes.json' + ] self.filepath = os.path.sep.join(path_items) self.types = {} self.object_type_ids = {} self.groups = {} self.security_roles = {} self.required_keys = ['key', 'label', 'type'] - self.type_posibilities = ['text', 'boolean', 'date', 'enumerator', - 'dynamic enumerator', 'number'] + self.type_posibilities = [ + 'text', 'boolean', 'date', 'enumerator', + 'dynamic enumerator', 'number' + ] def discover(self, session, entities, event): ''' diff --git a/pype/ftrack/actions/action_delete_unpublished.py b/pype/ftrack/actions/action_delete_unpublished.py index effa66072e..41f95cfb70 100644 --- a/pype/ftrack/actions/action_delete_unpublished.py +++ b/pype/ftrack/actions/action_delete_unpublished.py @@ -3,7 +3,7 @@ import argparse import logging import getpass import ftrack_api -from ftrack_action_handler import BaseAction +from pype.ftrack import BaseAction class VersionsCleanup(BaseAction): @@ -14,7 +14,6 @@ class VersionsCleanup(BaseAction): # Action label label = 'Versions cleanup' - def discover(self, session, entities, event): ''' Validation ''' @@ -34,13 +33,13 @@ class VersionsCleanup(BaseAction): session.delete(version) try: session.commit() - except: + except Exception: session.rollback() raise return { 'success': True, - 'message': 'removed hidden versions' + 'message': 'Hidden versions were removed' } diff --git a/pype/ftrack/actions/action_set_version.py b/pype/ftrack/actions/action_set_version.py index 416f4db960..9156f23055 100644 --- a/pype/ftrack/actions/action_set_version.py +++ b/pype/ftrack/actions/action_set_version.py @@ -1,9 +1,8 @@ import sys import argparse import logging -import getpass import ftrack_api -from ftrack_action_handler import BaseAction +from pype.ftrack import BaseAction class SetVersion(BaseAction): @@ -11,11 +10,9 @@ class SetVersion(BaseAction): #: Action identifier. identifier = 'version.set' - #: Action label. label = 'Version Set' - def discover(self, session, entities, event): ''' Validation ''' @@ -49,23 +46,24 @@ class SetVersion(BaseAction): # Do something with the values or return a new form. values = event['data'].get('values', {}) # Default is action True - scs = True - msg = 'Version was changed to v{0}'.format(values['version_number']) + scs = False if not values['version_number']: - scs = False, - msg = "You didn't enter any version." + msg = 'You didn\'t enter any version.' elif int(values['version_number']) <= 0: - scs = False msg = 'Negative or zero version is not valid.' else: - entity['version'] = values['version_number'] - - try: - session.commit() - except: - session.rollback() - raise + try: + entity['version'] = values['version_number'] + session.commit() + msg = 'Version was changed to v{0}'.format( + values['version_number'] + ) + scs = True + except Exception as e: + msg = 'Unexpected error occurs during version set ({})'.format( + str(e) + ) return { 'success': scs, diff --git a/pype/ftrack/actions/action_sync_to_avalon_local.py b/pype/ftrack/actions/action_sync_to_avalon_local.py index 347172acb1..35a3e333b1 100644 --- a/pype/ftrack/actions/action_sync_to_avalon_local.py +++ b/pype/ftrack/actions/action_sync_to_avalon_local.py @@ -6,7 +6,7 @@ import json import importlib import ftrack_api -from ftrack_action_handler import BaseAction +from pype.ftrack import BaseAction from pype.ftrack import ftrack_utils @@ -177,18 +177,15 @@ class SyncToAvalon(BaseAction): avalon_project = result['project'] job['status'] = 'done' - session.commit() self.log.info('Synchronization to Avalon was successfull!') except ValueError as ve: job['status'] = 'failed' - session.commit() message = str(ve) self.log.error('Error during syncToAvalon: {}'.format(message)) except Exception as e: job['status'] = 'failed' - session.commit() exc_type, exc_obj, exc_tb = sys.exc_info() fname = os.path.split(exc_tb.tb_frame.f_code.co_filename)[1] log_message = "{}/{}/Line: {}".format( @@ -201,6 +198,10 @@ class SyncToAvalon(BaseAction): 'Unexpected Error' ' - Please check Log for more information' ) + finally: + if job['status'] in ['queued', 'running']: + job['status'] = 'failed' + session.commit() if len(message) > 0: message = "Unable to sync: {}".format(message) diff --git a/pype/ftrack/actions/action_test.py b/pype/ftrack/actions/action_test.py index f40807a420..740f6ca223 100644 --- a/pype/ftrack/actions/action_test.py +++ b/pype/ftrack/actions/action_test.py @@ -9,7 +9,7 @@ import json import re import ftrack_api -from ftrack_action_handler import BaseAction +from pype.ftrack import BaseAction from avalon import io, inventory, schema diff --git a/pype/ftrack/actions/action_thumbToChildern.py b/pype/ftrack/actions/action_thumbToChildern.py index 52d31ee4e5..8b31c4b7e9 100644 --- a/pype/ftrack/actions/action_thumbToChildern.py +++ b/pype/ftrack/actions/action_thumbToChildern.py @@ -4,11 +4,11 @@ import sys import argparse import logging -import getpass import json import ftrack_api -from ftrack_action_handler import BaseAction +from pype.ftrack import BaseAction + class ThumbToChildren(BaseAction): '''Custom action.''' @@ -18,8 +18,10 @@ class ThumbToChildren(BaseAction): # Action label label = 'Thumbnail to Children' # Action icon - icon = "https://cdn3.iconfinder.com/data/icons/transfers/100/239322-download_transfer-128.png" - + icon = ( + 'https://cdn3.iconfinder.com/data/icons/transfers/100/' + '239322-download_transfer-128.png' + ) def discover(self, session, entities, event): ''' Validation ''' @@ -29,7 +31,6 @@ class ThumbToChildren(BaseAction): return True - def launch(self, session, entities, event): '''Callback method for action.''' @@ -53,7 +54,7 @@ class ThumbToChildren(BaseAction): # inform the user that the job is done job['status'] = 'done' - except: + except Exception: # fail the job if something goes wrong job['status'] = 'failed' raise @@ -66,7 +67,6 @@ class ThumbToChildren(BaseAction): } - def register(session, **kw): '''Register action. Called when used as an event plugin.''' if not isinstance(session, ftrack_api.session.Session): @@ -75,6 +75,7 @@ def register(session, **kw): action_handler = ThumbToChildren(session) action_handler.register() + def main(arguments=None): '''Set up logging and register action.''' if arguments is None: diff --git a/pype/ftrack/actions/action_thumbToParent.py b/pype/ftrack/actions/action_thumbToParent.py index 73931abad8..56d2c94a46 100644 --- a/pype/ftrack/actions/action_thumbToParent.py +++ b/pype/ftrack/actions/action_thumbToParent.py @@ -4,10 +4,10 @@ import sys import argparse import logging -import getpass import json import ftrack_api -from ftrack_action_handler import BaseAction +from pype.ftrack import BaseAction + class ThumbToParent(BaseAction): '''Custom action.''' @@ -17,8 +17,10 @@ class ThumbToParent(BaseAction): # Action label label = 'Thumbnail to Parent' # Action icon - icon = "https://cdn3.iconfinder.com/data/icons/transfers/100/239419-upload_transfer-512.png" - + icon = ( + "https://cdn3.iconfinder.com/data/icons/transfers/100/" + "239419-upload_transfer-512.png" + ) def discover(self, session, entities, event): '''Return action config if triggered on asset versions.''' @@ -28,7 +30,6 @@ class ThumbToParent(BaseAction): return True - def launch(self, session, entities, event): '''Callback method for action.''' @@ -50,14 +51,19 @@ class ThumbToParent(BaseAction): if entity.entity_type.lower() == 'assetversion': try: parent = entity['task'] - except: + except Exception: par_ent = entity['link'][-2] parent = session.get(par_ent['type'], par_ent['id']) else: try: parent = entity['parent'] - except: - self.log.error("Durin Action 'Thumb to Parent' went something wrong") + except Exception as e: + msg = ( + "Durin Action 'Thumb to Parent'" + " went something wrong" + ) + self.log.error(msg) + raise e thumbid = entity['thumbnail_id'] if parent and thumbid: @@ -69,10 +75,10 @@ class ThumbToParent(BaseAction): # inform the user that the job is done job['status'] = status or 'done' - except: + except Exception as e: # fail the job if something goes wrong job['status'] = 'failed' - raise + raise e finally: session.commit() @@ -91,6 +97,7 @@ def register(session, **kw): action_handler = ThumbToParent(session) action_handler.register() + def main(arguments=None): '''Set up logging and register action.''' if arguments is None: diff --git a/pype/ftrack/actions/djvview.py b/pype/ftrack/actions/djvview.py index 549a439955..662b26cfd4 100644 --- a/pype/ftrack/actions/djvview.py +++ b/pype/ftrack/actions/djvview.py @@ -75,15 +75,23 @@ class DJVViewAction(object): self.session.api_user ), self.discover ) - + launch_subscription = ( + 'topic=ftrack.action.launch' + ' and data.actionIdentifier={0}' + ' and source.user.username={1}' + ) self.session.event_hub.subscribe( - 'topic=ftrack.action.launch and data.actionIdentifier={0} and source.user.username={1}'.format( + launch_subscription.format( self.identifier, self.session.api_user ), self.launch ) - self.log.info("----- action - <" + self.__class__.__name__ + "> - Has been registered -----") + self.log.info( + "Action '{}' - Registered successfully".format( + self.__class__.__name__ + ) + ) def get_applications(self): applications = [] @@ -115,16 +123,17 @@ class DJVViewAction(object): if not os.path.exists(start): raise ValueError( - 'First part "{0}" of expression "{1}" must match exactly to an ' - 'existing entry on the filesystem.' + 'First part "{0}" of expression "{1}" must match exactly to an' + ' existing entry on the filesystem.' .format(start, expression) ) - expressions = list(map(re.compile, pieces)) expressionsCount = len(expression)-1 - for location, folders, files in os.walk(start, topdown=True, followlinks=True): + for location, folders, files in os.walk( + start, topdown=True, followlinks=True + ): level = location.rstrip(os.path.sep).count(os.path.sep) expression = expressions[level] @@ -158,8 +167,8 @@ class DJVViewAction(object): else: self.logger.debug( 'Discovered application executable, but it ' - 'does not appear to o contain required version ' - 'information: {0}'.format(path) + 'does not appear to o contain required version' + ' information: {0}'.format(path) ) # Don't descend any further as out of patterns to match. @@ -175,7 +184,9 @@ class DJVViewAction(object): entities = list() for entity in selection: entities.append( - (session.get(self.get_entity_type(entity), entity.get('entityId'))) + (session.get( + self.get_entity_type(entity), entity.get('entityId') + )) ) return entities @@ -213,7 +224,7 @@ class DJVViewAction(object): # TODO Is this proper way? try: fps = int(entities[0]['custom_attributes']['fps']) - except: + except Exception: fps = 24 # TODO issequence is probably already built-in validation in ftrack @@ -239,29 +250,46 @@ class DJVViewAction(object): range = (padding % start) + '-' + (padding % end) filename = re.sub('%[0-9]*d', range, filename) else: + msg = ( + 'DJV View - Filename has more than one' + ' sequence identifier.' + ) return { 'success': False, - 'message': 'DJV View - Filename has more than one seqence identifier.' + 'message': (msg) } cmd = [] # DJV path cmd.append(os.path.normpath(self.djv_path)) # DJV Options Start ############################################## - # cmd.append('-file_layer (value)') #layer name - cmd.append('-file_proxy 1/2') # Proxy scale: 1/2, 1/4, 1/8 - cmd.append('-file_cache True') # Cache: True, False. - # cmd.append('-window_fullscreen') #Start in full screen - # cmd.append("-window_toolbar False") # Toolbar controls: False, True. - # cmd.append("-window_playbar False") # Window controls: False, True. - # cmd.append("-view_grid None") # Grid overlay: None, 1x1, 10x10, 100x100. - # cmd.append("-view_hud True") # Heads up display: True, False. - cmd.append("-playback Forward") # Playback: Stop, Forward, Reverse. - # cmd.append("-playback_frame (value)") # Frame. + '''layer name''' + # cmd.append('-file_layer (value)') + ''' Proxy scale: 1/2, 1/4, 1/8''' + cmd.append('-file_proxy 1/2') + ''' Cache: True, False.''' + cmd.append('-file_cache True') + ''' Start in full screen ''' + # cmd.append('-window_fullscreen') + ''' Toolbar controls: False, True.''' + # cmd.append("-window_toolbar False") + ''' Window controls: False, True.''' + # cmd.append("-window_playbar False") + ''' Grid overlay: None, 1x1, 10x10, 100x100.''' + # cmd.append("-view_grid None") + ''' Heads up display: True, False.''' + # cmd.append("-view_hud True") + ''' Playback: Stop, Forward, Reverse.''' + cmd.append("-playback Forward") + ''' Frame.''' + # cmd.append("-playback_frame (value)") cmd.append("-playback_speed " + str(fps)) - # cmd.append("-playback_timer (value)") # Timer: Sleep, Timeout. Value: Sleep. - # cmd.append("-playback_timer_resolution (value)") # Timer resolution (seconds): 0.001. - cmd.append("-time_units Frames") # Time units: Timecode, Frames. + ''' Timer: Sleep, Timeout. Value: Sleep.''' + # cmd.append("-playback_timer (value)") + ''' Timer resolution (seconds): 0.001.''' + # cmd.append("-playback_timer_resolution (value)") + ''' Time units: Timecode, Frames.''' + cmd.append("-time_units Frames") # DJV Options End ################################################ # PATH TO COMPONENT @@ -287,7 +315,7 @@ class DJVViewAction(object): if entity['components'][0]['file_type'] in allowed_types: versions.append(entity) - if entity.entity_type.lower() == "task": + elif entity.entity_type.lower() == "task": # AssetVersions are obtainable only from shot! shotentity = entity['parent'] @@ -297,7 +325,8 @@ class DJVViewAction(object): if version['task']['id'] != entity['id']: continue # Get only components with allowed type - if version['components'][0]['file_type'] in allowed_types: + filetype = version['components'][0]['file_type'] + if filetype in allowed_types: versions.append(version) # Raise error if no components were found @@ -323,7 +352,7 @@ class DJVViewAction(object): # if component.getMembers(): # frame = int(component.getMembers()[0].getName()) # file_path = file_path % frame - except: + except Exception: # This works but is NOT proper way file_path = component['component_locations'][0]['resource_identifier'] @@ -367,6 +396,7 @@ def main(arguments=None): if arguments is None: arguments = [] + import argparse parser = argparse.ArgumentParser() # Allow setting of logging level from arguments. loggingLevels = {} diff --git a/pype/ftrack/event_server.py b/pype/ftrack/event_server.py index acc83fce0e..9c6207d6a2 100644 --- a/pype/ftrack/event_server.py +++ b/pype/ftrack/event_server.py @@ -1,17 +1,17 @@ import sys -import os from pype.ftrack import credentials, login_dialog as login_dialog from FtrackServer import FtrackServer -from app.vendor.Qt import QtCore, QtGui, QtWidgets +from app.vendor.Qt import QtWidgets from pype import api log = api.Logger.getLogger(__name__, "ftrack-event-server") + class EventServer: def __init__(self): self.login_widget = login_dialog.Login_Dialog_ui(self) self.event_server = FtrackServer('event') - + cred = credentials._get_credentials() if 'username' in cred and 'apiKey' in cred: @@ -27,10 +27,12 @@ class EventServer: self.login_widget.close() self.event_server.run_server() + def main(): app = QtWidgets.QApplication(sys.argv) event = EventServer() sys.exit(app.exec_()) + if (__name__ == ('__main__')): main() diff --git a/pype/ftrack/login_dialog.py b/pype/ftrack/login_dialog.py index 8365a6f3ab..c4011b0169 100644 --- a/pype/ftrack/login_dialog.py +++ b/pype/ftrack/login_dialog.py @@ -1,4 +1,3 @@ -import sys import os import requests from app.vendor.Qt import QtCore, QtGui, QtWidgets @@ -23,9 +22,9 @@ class Login_Dialog_ui(QtWidgets.QWidget): self.parent = parent - if hasattr(parent,'icon'): + if hasattr(parent, 'icon'): self.setWindowIcon(self.parent.icon) - elif hasattr(parent,'parent') and hasattr(parent.parent,'icon'): + elif hasattr(parent, 'parent') and hasattr(parent.parent, 'icon'): self.setWindowIcon(self.parent.parent.icon) else: pype_setup = os.getenv('PYPE_SETUP_ROOT') @@ -34,7 +33,10 @@ class Login_Dialog_ui(QtWidgets.QWidget): icon = QtGui.QIcon(fname) self.setWindowIcon(icon) - self.setWindowFlags(QtCore.Qt.WindowCloseButtonHint | QtCore.Qt.WindowMinimizeButtonHint) + self.setWindowFlags( + QtCore.Qt.WindowCloseButtonHint | + QtCore.Qt.WindowMinimizeButtonHint + ) self.loginSignal.connect(self.loginWithCredentials) self._translate = QtCore.QCoreApplication.translate @@ -85,7 +87,9 @@ class Login_Dialog_ui(QtWidgets.QWidget): self.user_input.setEnabled(True) self.user_input.setFrame(True) self.user_input.setObjectName("user_input") - self.user_input.setPlaceholderText(self._translate("main","user.name")) + self.user_input.setPlaceholderText( + self._translate("main", "user.name") + ) self.user_input.textChanged.connect(self._user_changed) self.api_label = QtWidgets.QLabel("API Key:") @@ -98,19 +102,21 @@ class Login_Dialog_ui(QtWidgets.QWidget): self.api_input.setEnabled(True) self.api_input.setFrame(True) self.api_input.setObjectName("api_input") - self.api_input.setPlaceholderText(self._translate("main","e.g. xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx")) + self.api_input.setPlaceholderText(self._translate( + "main", "e.g. xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" + )) self.api_input.textChanged.connect(self._api_changed) self.error_label = QtWidgets.QLabel("") self.error_label.setFont(self.font) self.error_label.setTextFormat(QtCore.Qt.RichText) self.error_label.setObjectName("error_label") - self.error_label.setWordWrap(True); + self.error_label.setWordWrap(True) self.error_label.hide() self.form.addRow(self.ftsite_label, self.ftsite_input) self.form.addRow(self.user_label, self.user_input) - self.form.addRow(self.api_label,self.api_input) + self.form.addRow(self.api_label, self.api_input) self.form.addRow(self.error_label) self.btnGroup = QtWidgets.QHBoxLayout() @@ -118,7 +124,9 @@ class Login_Dialog_ui(QtWidgets.QWidget): self.btnGroup.setObjectName("btnGroup") self.btnEnter = QtWidgets.QPushButton("Login") - self.btnEnter.setToolTip('Set Username and API Key with entered values') + self.btnEnter.setToolTip( + 'Set Username and API Key with entered values' + ) self.btnEnter.clicked.connect(self.enter_credentials) self.btnClose = QtWidgets.QPushButton("Close") @@ -157,7 +165,7 @@ class Login_Dialog_ui(QtWidgets.QWidget): self.ftsite_input.setText(newurl) - except Exception as e: + except Exception: self.setError("FTRACK_SERVER is not set in templates") self.btnEnter.setEnabled(False) self.btnFtrack.setEnabled(False) @@ -174,7 +182,7 @@ class Login_Dialog_ui(QtWidgets.QWidget): def _api_changed(self): self.api_input.setStyleSheet("") - def _invalid_input(self,entity): + def _invalid_input(self, entity): entity.setStyleSheet("border: 1px solid red;") def enter_credentials(self): @@ -205,11 +213,13 @@ class Login_Dialog_ui(QtWidgets.QWidget): else: self._invalid_input(self.user_input) self._invalid_input(self.api_input) - self.setError("We're unable to sign in to Ftrack with these credentials") + self.setError( + "We're unable to sign in to Ftrack with these credentials" + ) def open_ftrack(self): url = self.ftsite_input.text() - self.loginWithCredentials(url,None,None) + self.loginWithCredentials(url, None, None) def checkUrl(self, url): url = url.strip('/ ') @@ -218,7 +228,7 @@ class Login_Dialog_ui(QtWidgets.QWidget): self.setError("There is no URL set in Templates") return - if not 'http' in url: + if 'http' not in url: if url.endswith('ftrackapp.com'): url = 'https://' + url else: @@ -226,7 +236,8 @@ class Login_Dialog_ui(QtWidgets.QWidget): try: result = requests.get( url, - allow_redirects=False # Old python API will not work with redirect. + # Old python API will not work with redirect. + allow_redirects=False ) except requests.exceptions.RequestException: self.setError( @@ -234,7 +245,6 @@ class Login_Dialog_ui(QtWidgets.QWidget): ) return - if ( result.status_code != 200 or 'FTRACK_VERSION' not in result.headers ): @@ -254,7 +264,7 @@ class Login_Dialog_ui(QtWidgets.QWidget): ) return - if not 'http' in url: + if 'http' not in url: if url.endswith('ftrackapp.com'): url = 'https://' + url else: @@ -262,7 +272,8 @@ class Login_Dialog_ui(QtWidgets.QWidget): try: result = requests.get( url, - allow_redirects=False # Old python API will not work with redirect. + # Old python API will not work with redirect. + allow_redirects=False ) except requests.exceptions.RequestException: self.setError( @@ -270,7 +281,6 @@ class Login_Dialog_ui(QtWidgets.QWidget): ) return - if ( result.status_code != 200 or 'FTRACK_VERSION' not in result.headers ): From d5829804a1e13bbdf40eda8bca43ea51c2573b57 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Mon, 21 Jan 2019 13:46:19 +0100 Subject: [PATCH 04/27] job killer improved - with interface --- ...illRunningJobs.py => action_job_killer.py} | 75 ++++++++++++++----- 1 file changed, 58 insertions(+), 17 deletions(-) rename pype/ftrack/actions/{action_killRunningJobs.py => action_job_killer.py} (53%) diff --git a/pype/ftrack/actions/action_killRunningJobs.py b/pype/ftrack/actions/action_job_killer.py similarity index 53% rename from pype/ftrack/actions/action_killRunningJobs.py rename to pype/ftrack/actions/action_job_killer.py index 3e82938491..184053ed47 100644 --- a/pype/ftrack/actions/action_killRunningJobs.py +++ b/pype/ftrack/actions/action_job_killer.py @@ -4,48 +4,89 @@ import sys import argparse import logging -import datetime import ftrack_api -from ftrack_action_handler import BaseAction +from pype.ftrack import BaseAction class JobKiller(BaseAction): '''Edit meta data action.''' #: Action identifier. - identifier = 'job.kill' + identifier = 'job.killer' #: Action label. label = 'Job Killer' #: Action description. description = 'Killing all running jobs younger than day' - def discover(self, session, entities, event): ''' Validation ''' return True + def interface(self, session, entities, event): + if not event['data'].get('values', {}): + title = 'Select jobs to kill' + + jobs = session.query( + 'select id, status from Job' + ' where status in ("queued", "running")' + ) + + items = [] + import json + for job in jobs: + data = json.loads(job['data']) + user = job['user']['username'] + created = job['created_at'].strftime('%d.%m.%Y %H:%M:%S') + label = '{}/ {}/ {}'.format( + data['description'], created, user + ) + item = { + 'label': label, + 'name': job['id'], + 'type': 'boolean', + 'value': False + } + items.append(item) + + return { + 'items': items, + 'title': title + } def launch(self, session, entities, event): """ GET JOB """ + if 'values' not in event['data']: + return - yesterday = datetime.date.today() - datetime.timedelta(days=1) + values = event['data']['values'] + if len(values) <= 0: + return { + 'success': True, + 'message': 'No jobs to kill!' + } + jobs = [] + job_ids = [] - jobs = session.query( - 'select id, status from Job ' - 'where status in ("queued", "running") and created_at > {0}'.format(yesterday) - ) + for k, v in values.items(): + if v is True: + job_ids.append(k) + for id in job_ids: + query = 'Job where id is "{}"'.format(id) + jobs.append(session.query(query).one()) # Update all the queried jobs, setting the status to failed. for job in jobs: - self.log.debug(job['created_at']) - self.log.debug('Changing Job ({}) status: {} -> failed'.format(job['id'], job['status'])) - job['status'] = 'failed' - - try: - session.commit() - except: - session.rollback() + try: + job['status'] = 'failed' + session.commit() + self.log.debug(( + 'Changing Job ({}) status: {} -> failed' + ).format(job['id'], job['status'])) + except Exception: + self.warning.debug(( + 'Changing Job ({}) has failed' + ).format(job['id'])) self.log.info('All running jobs were killed Successfully!') return { From 730db909cf4dfff938c038160b6be78bdfbc4dd6 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Mon, 21 Jan 2019 13:46:44 +0100 Subject: [PATCH 05/27] removed unused ft_utils from actions --- pype/ftrack/actions/ft_utils.py | 430 -------------------------------- 1 file changed, 430 deletions(-) delete mode 100644 pype/ftrack/actions/ft_utils.py diff --git a/pype/ftrack/actions/ft_utils.py b/pype/ftrack/actions/ft_utils.py deleted file mode 100644 index f6f9bfc59b..0000000000 --- a/pype/ftrack/actions/ft_utils.py +++ /dev/null @@ -1,430 +0,0 @@ -import os -import operator -import ftrack_api -import collections -import sys -import json -import base64 - - -ignore_me = True -# sys.path.append(os.path.dirname(os.path.dirname(__file__))) -# from ftrack_kredenc.lucidity.vendor import yaml -# from ftrack_kredenc import lucidity -# -# -# def get_ftrack_connect_path(): -# -# ftrack_connect_root = os.path.abspath(os.getenv('FTRACK_CONNECT_PACKAGE')) -# -# return ftrack_connect_root -# -# -# def from_yaml(filepath): -# ''' Parse a Schema from a YAML file at the given *filepath*. -# ''' -# with open(filepath, 'r') as f: -# data = yaml.safe_load(f) -# return data -# -# -# def get_task_enviro(entity, environment=None): -# -# context = get_context(entity) -# -# if not environment: -# environment = {} -# -# for key in context: -# os.environ[key.upper()] = context[key]['name'] -# environment[key.upper()] = context[key]['name'] -# -# if key == 'Project': -# os.putenv('PROJECT_ROOT', context[key]['root']) -# os.environ['PROJECT_ROOT'] = context[key]['root'] -# environment['PROJECT_ROOT'] = context[key]['root'] -# print('PROJECT_ROOT: ' + context[key]['root']) -# print(key + ': ' + context[key]['name']) -# -# return environment -# -# -# def get_entity(): -# decodedEventData = json.loads( -# base64.b64decode( -# os.environ.get('FTRACK_CONNECT_EVENT') -# ) -# ) -# -# entity = decodedEventData.get('selection')[0] -# -# if entity['entityType'] == 'task': -# return ftrack_api.Task(entity['entityId']) -# else: -# return None -# -# -# def set_env_vars(): -# -# entity = get_entity() -# -# if entity: -# if not os.environ.get('project_root'): -# enviro = get_task_enviro(entity) -# -# print(enviro) -# -# -def get_context(entity): - - entityName = entity['name'] - entityId = entity['id'] - entityType = entity.entity_type - entityDescription = entity['description'] - - print(100*"*") - for k in entity['ancestors']: - print(k['name']) - print(100*"*") - hierarchy = entity.getParents() - - ctx = collections.OrderedDict() - - if entity.get('entityType') == 'task' and entityType == 'Task': - taskType = entity.getType().getName() - entityDic = { - 'type': taskType, - 'name': entityName, - 'id': entityId, - 'description': entityDescription - } - elif entity.get('entityType') == 'task': - entityDic = { - 'name': entityName, - 'id': entityId, - 'description': entityDescription - } - - ctx[entityType] = entityDic - - folder_counter = 0 - - for ancestor in hierarchy: - tempdic = {} - if isinstance(ancestor, ftrack_api.Component): - # Ignore intermediate components. - continue - - tempdic['name'] = ancestor.getName() - tempdic['id'] = ancestor.getId() - - try: - objectType = ancestor.getObjectType() - tempdic['description'] = ancestor.getDescription() - except AttributeError: - objectType = 'Project' - tempdic['description'] = '' - - if objectType == 'Asset Build': - tempdic['type'] = ancestor.getType().get('name') - objectType = objectType.replace(' ', '_') - elif objectType == 'Project': - tempdic['code'] = tempdic['name'] - tempdic['name'] = ancestor.get('fullname') - tempdic['root'] = ancestor.getRoot() - - if objectType == 'Folder': - objectType = objectType + str(folder_counter) - folder_counter += 1 - ctx[objectType] = tempdic - - return ctx - - -def getNewContext(entity): - - parents = [] - item = entity - while True: - item = item['parent'] - if not item: - break - parents.append(item) - - ctx = collections.OrderedDict() - - entityDic = { - 'name': entity['name'], - 'id': entity['id'], - } - try: - entityDic['type'] = entity['type']['name'] - except: - pass - - ctx[entity['object_type']['name']] = entityDic - - print(100*"-") - for p in parents: - print(p) - # add all parents to the context - for parent in parents: - tempdic = {} - if not parent.get('project_schema'): - tempdic = { - 'name': parent['full_name'], - 'code': parent['name'], - 'id': parent['id'], - } - tempdic = { - 'name': parent['name'], - 'id': parent['id'], - } - object_type = parent['object_type']['name'] - - ctx[object_type] = tempdic - - # add project to the context - project = entity['project'] - ctx['Project'] = { - 'name': project['full_name'], - 'code': project['name'], - 'id': project['id'], - 'root': project['root'], - }, - - return ctx -# -# -# def get_frame_range(): -# -# entity = get_entity() -# entityType = entity.getObjectType() -# environment = {} -# -# if entityType == 'Task': -# try: -# environment['FS'] = str(int(entity.getFrameStart())) -# except Exception: -# environment['FS'] = '1' -# try: -# environment['FE'] = str(int(entity.getFrameEnd())) -# except Exception: -# environment['FE'] = '1' -# else: -# try: -# environment['FS'] = str(int(entity.getFrameStart())) -# except Exception: -# environment['FS'] = '1' -# try: -# environment['FE'] = str(int(entity.getFrameEnd())) -# except Exception: -# environment['FE'] = '1' -# -# -# def get_asset_name_by_id(id): -# for t in ftrack_api.getAssetTypes(): -# try: -# if t.get('typeid') == id: -# return t.get('name') -# except: -# return None -# -# -# def get_status_by_name(name): -# statuses = ftrack_api.getTaskStatuses() -# -# result = None -# for s in statuses: -# if s.get('name').lower() == name.lower(): -# result = s -# -# return result -# -# -# def sort_types(types): -# data = {} -# for t in types: -# data[t] = t.get('sort') -# -# data = sorted(data.items(), key=operator.itemgetter(1)) -# results = [] -# for item in data: -# results.append(item[0]) -# -# return results -# -# -# def get_next_task(task): -# shot = task.getParent() -# tasks = shot.getTasks() -# -# types_sorted = sort_types(ftrack_api.getTaskTypes()) -# -# next_types = None -# for t in types_sorted: -# if t.get('typeid') == task.get('typeid'): -# try: -# next_types = types_sorted[(types_sorted.index(t) + 1):] -# except: -# pass -# -# for nt in next_types: -# for t in tasks: -# if nt.get('typeid') == t.get('typeid'): -# return t -# -# return None -# -# -# def get_latest_version(versions): -# latestVersion = None -# if len(versions) > 0: -# versionNumber = 0 -# for item in versions: -# if item.get('version') > versionNumber: -# versionNumber = item.getVersion() -# latestVersion = item -# return latestVersion -# -# -# def get_thumbnail_recursive(task): -# if task.get('thumbid'): -# thumbid = task.get('thumbid') -# return ftrack_api.Attachment(id=thumbid) -# if not task.get('thumbid'): -# parent = ftrack_api.Task(id=task.get('parent_id')) -# return get_thumbnail_recursive(parent) -# -# -# # paths_collected -# -# def getFolderHierarchy(context): -# '''Return structure for *hierarchy*. -# ''' -# -# hierarchy = [] -# for key in reversed(context): -# hierarchy.append(context[key]['name']) -# print(hierarchy) -# -# return os.path.join(*hierarchy[1:-1]) -# -# -def tweakContext(context, include=False): - - for key in context: - if key == 'Asset Build': - context['Asset_Build'] = context.pop(key) - key = 'Asset_Build' - description = context[key].get('description') - if description: - context[key]['description'] = '_' + description - - hierarchy = [] - for key in reversed(context): - hierarchy.append(context[key]['name']) - - if include: - hierarchy = os.path.join(*hierarchy[1:]) - else: - hierarchy = os.path.join(*hierarchy[1:-1]) - - context['ft_hierarchy'] = hierarchy - - -def getSchema(entity): - - project = entity['project'] - schema = project['project_schema']['name'] - - tools = os.path.abspath(os.environ.get('studio_tools')) - - schema_path = os.path.join(tools, 'studio', 'templates', (schema + '_' + project['name'] + '.yml')) - if not os.path.exists(schema_path): - schema_path = os.path.join(tools, 'studio', 'templates', (schema + '.yml')) - if not os.path.exists(schema_path): - schema_path = os.path.join(tools, 'studio', 'templates', 'default.yml') - - schema = lucidity.Schema.from_yaml(schema_path) - - print(schema_path) - return schema - - -# def getAllPathsYaml(entity, root=''): -# -# if isinstance(entity, str) or isinstance(entity, unicode): -# entity = ftrack_api.Task(entity) -# -# context = get_context(entity) -# -# tweakContext(context) -# -# schema = getSchema(entity) -# -# paths = schema.format_all(context) -# paths_collected = [] -# -# for path in paths: -# tweak_path = path[0].replace(" ", '_').replace('\'', '').replace('\\', '/') -# -# tempPath = os.path.join(root, tweak_path) -# path = list(path) -# path[0] = tempPath -# paths_collected.append(path) -# -# return paths_collected -# - -def getPathsYaml(entity, templateList=None, root=None, **kwargs): - ''' - version=None - ext=None - item=None - family=None - subset=None - ''' - - context = get_context(entity) - - if entity.entity_type != 'Task': - tweakContext(context, include=True) - else: - tweakContext(context) - - context.update(kwargs) - - host = sys.executable.lower() - - ext = None - if not context.get('ext'): - if "nuke" in host: - ext = 'nk' - elif "maya" in host: - ext = 'ma' - elif "houdini" in host: - ext = 'hip' - if ext: - context['ext'] = ext - - if not context.get('subset'): - context['subset'] = '' - else: - context['subset'] = '_' + context['subset'] - - schema = getSchema(entity) - paths = schema.format_all(context) - paths_collected = set([]) - for temp_mask in templateList: - for path in paths: - if temp_mask in path[1].name: - path = path[0].lower().replace(" ", '_').replace('\'', '').replace('\\', '/') - path_list = path.split('/') - if path_list[0].endswith(':'): - path_list[0] = path_list[0] + os.path.sep - path = os.path.join(*path_list) - temppath = os.path.join(root, path) - paths_collected.add(temppath) - - return list(paths_collected) From 11a4d1ac71b7208bb6dcd1e5cd56ceee7dbf78f5 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Mon, 21 Jan 2019 13:48:27 +0100 Subject: [PATCH 06/27] moved ftrack handlers from events/actions to ftrack_utils --- .../actions/action_folders_create_lucidity.py | 181 ---------- .../ftrack_utils/ftrack_action_handler.py | 318 +++++++++++++++++ .../ftrack_app_handler.py} | 329 +----------------- .../ftrack_event_handler.py | 24 +- 4 files changed, 342 insertions(+), 510 deletions(-) delete mode 100644 pype/ftrack/actions/action_folders_create_lucidity.py create mode 100644 pype/ftrack/ftrack_utils/ftrack_action_handler.py rename pype/ftrack/{actions/ftrack_action_handler.py => ftrack_utils/ftrack_app_handler.py} (60%) rename pype/ftrack/{events => ftrack_utils}/ftrack_event_handler.py (88%) diff --git a/pype/ftrack/actions/action_folders_create_lucidity.py b/pype/ftrack/actions/action_folders_create_lucidity.py deleted file mode 100644 index c1b9d9aba6..0000000000 --- a/pype/ftrack/actions/action_folders_create_lucidity.py +++ /dev/null @@ -1,181 +0,0 @@ -import logging -import os -import argparse -import sys -import errno - -import ftrack_api -from ftrack_action_handler import BaseAction -import json -from pype import api as pype - - -class CreateFolders(BaseAction): - - '''Custom action.''' - - #: Action identifier. - identifier = 'create.folders' - - #: Action label. - label = 'Create Folders' - - #: Action Icon. - icon = 'https://cdn1.iconfinder.com/data/icons/hawcons/32/698620-icon-105-folder-add-512.png' - - def discover(self, session, entities, event): - ''' Validation ''' - - # if (len(entities) == 0 or entities[0].entity_type not in - # ['Episode', 'Sequence', 'Shot', 'Folder', 'Asset Build']): - # return False - - return True - - def getShotAsset(self, entity): - if entity not in self.importable: - if entity['object_type']['name'] != 'Task': - self.importable.add(entity) - - if entity['children']: - children = entity['children'] - for child in children: - self.getShotAsset(child) - - def launch(self, session, entities, event): - '''Callback method for custom action.''' - - ####################################################################### - - # JOB SETTINGS - userId = event['source']['user']['id'] - user = session.query('User where id is ' + userId).one() - - job = session.create('Job', { - 'user': user, - 'status': 'running', - 'data': json.dumps({ - 'description': 'Creating Folders.' - }) - }) - - try: - self.importable = set([]) - # self.importable = [] - - self.Anatomy = pype.Anatomy - - project = entities[0]['project'] - - paths_collected = set([]) - - # get all child entities separately/unique - for entity in entities: - self.getShotAsset(entity) - - for ent in self.importable: - self.log.info("{}".format(ent['name'])) - - for entity in self.importable: - print(entity['name']) - - anatomy = pype.Anatomy - parents = entity['link'] - - hierarchy_names = [] - for p in parents[1:-1]: - hierarchy_names.append(p['name']) - - if hierarchy_names: - # hierarchy = os.path.sep.join(hierarchy) - hierarchy = os.path.join(*hierarchy_names) - - template_data = {"project": {"name": project['full_name'], - "code": project['name']}, - "asset": entity['name'], - "hierarchy": hierarchy} - - for task in entity['children']: - if task['object_type']['name'] == 'Task': - self.log.info('child: {}'.format(task['name'])) - template_data['task'] = task['name'] - anatomy_filled = anatomy.format(template_data) - paths_collected.add(anatomy_filled.work.folder) - paths_collected.add(anatomy_filled.publish.folder) - - for path in paths_collected: - self.log.info(path) - try: - os.makedirs(path) - except OSError as error: - if error.errno != errno.EEXIST: - raise - - job['status'] = 'done' - session.commit() - - except ValueError as ve: - job['status'] = 'failed' - session.commit() - message = str(ve) - self.log.error('Error during syncToAvalon: {}'.format(message)) - - except Exception as e: - job['status'] = 'failed' - session.commit() - - ####################################################################### - - return { - 'success': True, - 'message': 'Created Folders Successfully!' - } - - -def register(session, **kw): - '''Register plugin. Called when used as an plugin.''' - - if not isinstance(session, ftrack_api.session.Session): - return - - action_handler = CreateFolders(session) - action_handler.register() - - -def main(arguments=None): - '''Set up logging and register action.''' - if arguments is None: - arguments = [] - - parser = argparse.ArgumentParser() - # Allow setting of logging level from arguments. - loggingLevels = {} - for level in ( - logging.NOTSET, logging.DEBUG, logging.INFO, logging.WARNING, - logging.ERROR, logging.CRITICAL - ): - loggingLevels[logging.getLevelName(level).lower()] = level - - parser.add_argument( - '-v', '--verbosity', - help='Set the logging output verbosity.', - choices=loggingLevels.keys(), - default='info' - ) - namespace = parser.parse_args(arguments) - - # Set up basic logging - logging.basicConfig(level=loggingLevels[namespace.verbosity]) - - session = ftrack_api.Session() - register(session) - - # Wait for events - logging.info( - 'Registered actions and listening for events. Use Ctrl-C to abort.' - ) - session.event_hub.wait() - - -if __name__ == '__main__': - raise SystemExit(main(sys.argv[1:])) diff --git a/pype/ftrack/ftrack_utils/ftrack_action_handler.py b/pype/ftrack/ftrack_utils/ftrack_action_handler.py new file mode 100644 index 0000000000..60a1a9ae0e --- /dev/null +++ b/pype/ftrack/ftrack_utils/ftrack_action_handler.py @@ -0,0 +1,318 @@ +# :coding: utf-8 +# :copyright: Copyright (c) 2017 ftrack +import ftrack_api +from pype import api as pype + + +class BaseAction(object): + '''Custom Action base class + + `label` a descriptive string identifing your action. + + `varaint` To group actions together, give them the same + label and specify a unique variant per action. + + `identifier` a unique identifier for your action. + + `description` a verbose descriptive text for you action + + ''' + label = None + variant = None + identifier = None + description = None + icon = None + + def __init__(self, session): + '''Expects a ftrack_api.Session instance''' + + self.log = pype.Logger.getLogger(self.__class__.__name__) + + if self.label is None: + raise ValueError( + 'Action missing label.' + ) + + elif self.identifier is None: + raise ValueError( + 'Action missing identifier.' + ) + + self._session = session + + @property + def session(self): + '''Return current session.''' + return self._session + + def reset_session(self): + self.session.reset() + + def register(self, priority=100): + ''' + Registers the action, subscribing the the discover and launch topics. + - highest priority event will show last + ''' + self.session.event_hub.subscribe( + 'topic=ftrack.action.discover and source.user.username={0}'.format( + self.session.api_user + ), self._discover, priority=priority + ) + + launch_subscription = ( + 'topic=ftrack.action.launch' + ' and data.actionIdentifier={0}' + ' and source.user.username={1}' + ).format( + self.identifier, + self.session.api_user + ) + self.session.event_hub.subscribe( + launch_subscription, + self._launch + ) + + self.log.info("Action '{}' - Registered successfully".format( + self.__class__.__name__)) + + def _discover(self, event): + args = self._translate_event( + self.session, event + ) + + accepts = self.discover( + self.session, *args + ) + + if accepts: + self.log.debug(u'Discovering action with selection: {0}'.format( + args[1]['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 _translate_event(self, session, event): + '''Return *event* translated structure to be used with the API.''' + + _selection = event['data'].get('selection', []) + + _entities = list() + for entity in _selection: + _entities.append( + ( + session.get( + self._get_entity_type(entity), + entity.get('entityId') + ) + ) + ) + + return [ + _entities, + event + ] + + def _get_entity_type(self, entity): + '''Return translated entity type tht can be used with API.''' + # Get entity type and make sure it is lower cased. Most places except + # the component tab in the Sidebar will use lower case notation. + entity_type = entity.get('entityType').replace('_', '').lower() + + for schema in self.session.schemas: + alias_for = schema.get('alias_for') + + if ( + alias_for and isinstance(alias_for, str) and + alias_for.lower() == entity_type + ): + return schema['id'] + + for schema in self.session.schemas: + if schema['id'].lower() == entity_type: + return schema['id'] + + raise ValueError( + 'Unable to translate entity type: {0}.'.format(entity_type) + ) + + def _launch(self, event): + self.reset_session() + args = self._translate_event( + self.session, event + ) + + interface = self._interface( + self.session, *args + ) + + if interface: + return interface + + response = self.launch( + self.session, *args + ) + + return self._handle_result( + self.session, response, *args + ) + + def launch(self, session, entities, event): + '''Callback method for the custom action. + + return either a bool ( True if successful or False if the action failed ) + or a dictionary with they keys `message` and `success`, the message should be a + string and will be displayed as feedback to the user, success should be a bool, + True if successful or False if the action failed. + + *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 + + ''' + raise NotImplementedError() + + def _interface(self, *args): + interface = self.interface(*args) + if interface: + if 'items' in interface: + return interface + + return { + 'items': 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 show_message(self, event, input_message, result=False): + """ + Shows message to user who triggered event + - event - just source of user id + - input_message - message that is shown to user + - result - changes color of message (based on ftrack settings) + - True = Violet + - False = Red + """ + if not isinstance(result, bool): + result = False + + try: + message = str(input_message) + except Exception: + return + + user_id = event['source']['user']['id'] + target = ( + 'applicationId=ftrack.client.web and user.id="{0}"' + ).format(user_id) + self.session.event_hub.publish( + ftrack_api.event.base.Event( + topic='ftrack.action.trigger-user-interface', + data=dict( + type='message', + success=result, + message=message + ), + target=target + ), + on_error='ignore' + ) + + def _handle_result(self, session, result, entities, event): + '''Validate the returned result from the action callback''' + if isinstance(result, bool): + result = { + 'success': result, + 'message': ( + '{0} launched successfully.'.format( + self.label + ) + ) + } + + elif isinstance(result, dict): + if 'items' in result: + items = result['items'] + if not isinstance(items, list): + raise ValueError('Invalid items format, must be list!') + + else: + for key in ('success', 'message'): + if key in result: + continue + + raise KeyError( + 'Missing required key: {0}.'.format(key) + ) + + else: + self.log.error( + 'Invalid result type must be bool or dictionary!' + ) + + return result + + def show_interface(self, event, items, title=''): + """ + Shows interface to user who triggered event + - 'items' must be list containing Ftrack interface items + """ + user_id = event['source']['user']['id'] + target = ( + 'applicationId=ftrack.client.web and user.id="{0}"' + ).format(user_id) + + self.session.event_hub.publish( + ftrack_api.event.base.Event( + topic='ftrack.action.trigger-user-interface', + data=dict( + type='widget', + items=items, + title=title + ), + target=target + ), + on_error='ignore' + ) diff --git a/pype/ftrack/actions/ftrack_action_handler.py b/pype/ftrack/ftrack_utils/ftrack_app_handler.py similarity index 60% rename from pype/ftrack/actions/ftrack_action_handler.py rename to pype/ftrack/ftrack_utils/ftrack_app_handler.py index 1b31f5e27d..a4977f34c5 100644 --- a/pype/ftrack/actions/ftrack_action_handler.py +++ b/pype/ftrack/ftrack_utils/ftrack_app_handler.py @@ -3,16 +3,11 @@ import os import sys import platform -import ftrack_api from avalon import lib import acre -from pype.ftrack import ftrack_utils - -from pype.ftrack import ftrack_utils +from . import ftrack_utils from pype import api as pype - - -ignore_me = True +from pype import lib as pypelib class AppAction(object): @@ -136,7 +131,7 @@ class AppAction(object): ft_project = entity['project'] - database = ftrack_utils.get_avalon_database() + database = pypelib.get_avalon_database() project_name = ft_project['full_name'] avalon_project = database[project_name].find_one({ "type": "project" @@ -243,7 +238,7 @@ class AppAction(object): entity = session.get(entity, id) project_name = entity['project']['full_name'] - database = ftrack_utils.get_avalon_database() + database = pypelib.get_avalon_database() # Get current environments env_list = [ @@ -407,7 +402,9 @@ class AppAction(object): task['status'] = status session.commit() except Exception as e: - msg = "Status '{}' in config wasn't found on Ftrack".format(status_name) + msg = ( + 'Status "{}" in config wasn\'t found on Ftrack' + ).format(status_name) self.log.warning(msg) # Set origin avalon environments @@ -469,315 +466,3 @@ class AppAction(object): ) return result - - -class BaseAction(object): - '''Custom Action base class - - `label` a descriptive string identifing your action. - - `varaint` To group actions together, give them the same - label and specify a unique variant per action. - - `identifier` a unique identifier for your action. - - `description` a verbose descriptive text for you action - - ''' - label = None - variant = None - identifier = None - description = None - icon = None - - def __init__(self, session): - '''Expects a ftrack_api.Session instance''' - - self.log = pype.Logger.getLogger(self.__class__.__name__) - - if self.label is None: - raise ValueError( - 'Action missing label.' - ) - - elif self.identifier is None: - raise ValueError( - 'Action missing identifier.' - ) - - self._session = session - - @property - def session(self): - '''Return current session.''' - return self._session - - def reset_session(self): - self.session.reset() - - def register(self, priority=100): - ''' - Registers the action, subscribing the the discover and launch topics. - - highest priority event will show last - ''' - self.session.event_hub.subscribe( - 'topic=ftrack.action.discover and source.user.username={0}'.format( - self.session.api_user - ), self._discover, priority=priority - ) - - launch_subscription = ( - 'topic=ftrack.action.launch' - ' and data.actionIdentifier={0}' - ' and source.user.username={1}' - ).format( - self.identifier, - self.session.api_user - ) - self.session.event_hub.subscribe( - launch_subscription, - self._launch - ) - - self.log.info("Action '{}' - Registered successfully".format( - self.__class__.__name__)) - - def _discover(self, event): - args = self._translate_event( - self.session, event - ) - - accepts = self.discover( - self.session, *args - ) - - if accepts: - self.log.info(u'Discovering action with selection: {0}'.format( - args[1]['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 _translate_event(self, session, event): - '''Return *event* translated structure to be used with the API.''' - - _selection = event['data'].get('selection', []) - - _entities = list() - for entity in _selection: - _entities.append( - ( - session.get( - self._get_entity_type(entity), - entity.get('entityId') - ) - ) - ) - - return [ - _entities, - event - ] - - def _get_entity_type(self, entity): - '''Return translated entity type tht can be used with API.''' - # Get entity type and make sure it is lower cased. Most places except - # the component tab in the Sidebar will use lower case notation. - entity_type = entity.get('entityType').replace('_', '').lower() - - for schema in self.session.schemas: - alias_for = schema.get('alias_for') - - if ( - alias_for and isinstance(alias_for, str) and - alias_for.lower() == entity_type - ): - return schema['id'] - - for schema in self.session.schemas: - if schema['id'].lower() == entity_type: - return schema['id'] - - raise ValueError( - 'Unable to translate entity type: {0}.'.format(entity_type) - ) - - def _launch(self, event): - self.reset_session() - args = self._translate_event( - self.session, event - ) - - interface = self._interface( - self.session, *args - ) - - if interface: - return interface - - response = self.launch( - self.session, *args - ) - - return self._handle_result( - self.session, response, *args - ) - - def launch(self, session, entities, event): - '''Callback method for the custom action. - - return either a bool ( True if successful or False if the action failed ) - or a dictionary with they keys `message` and `success`, the message should be a - string and will be displayed as feedback to the user, success should be a bool, - True if successful or False if the action failed. - - *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 - - ''' - raise NotImplementedError() - - def _interface(self, *args): - interface = self.interface(*args) - - if interface: - return { - 'items': 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 show_message(self, event, input_message, result=False): - """ - Shows message to user who triggered event - - event - just source of user id - - input_message - message that is shown to user - - result - changes color of message (based on ftrack settings) - - True = Violet - - False = Red - """ - if not isinstance(result, bool): - result = False - - try: - message = str(input_message) - except Exception: - return - - user_id = event['source']['user']['id'] - target = ( - 'applicationId=ftrack.client.web and user.id="{0}"' - ).format(user_id) - self.session.event_hub.publish( - ftrack_api.event.base.Event( - topic='ftrack.action.trigger-user-interface', - data=dict( - type='message', - success=result, - message=message - ), - target=target - ), - on_error='ignore' - ) - - def _handle_result(self, session, result, entities, event): - '''Validate the returned result from the action callback''' - if isinstance(result, bool): - result = { - 'success': result, - 'message': ( - '{0} launched successfully.'.format( - self.label - ) - ) - } - - elif isinstance(result, dict): - if 'items' in result: - items = result['items'] - if not isinstance(items, list): - raise ValueError('Invalid items format, must be list!') - - else: - for key in ('success', 'message'): - if key in result: - continue - - raise KeyError( - 'Missing required key: {0}.'.format(key) - ) - - else: - self.log.error( - 'Invalid result type must be bool or dictionary!' - ) - - return result - - def show_interface(self, event, items, title=''): - """ - Shows interface to user who triggered event - - 'items' must be list containing Ftrack interface items - """ - user_id = event['source']['user']['id'] - target = ( - 'applicationId=ftrack.client.web and user.id="{0}"' - ).format(user_id) - - self.session.event_hub.publish( - ftrack_api.event.base.Event( - topic='ftrack.action.trigger-user-interface', - data=dict( - type='widget', - items=items, - title=title - ), - target=target - ), - on_error='ignore' - ) diff --git a/pype/ftrack/events/ftrack_event_handler.py b/pype/ftrack/ftrack_utils/ftrack_event_handler.py similarity index 88% rename from pype/ftrack/events/ftrack_event_handler.py rename to pype/ftrack/ftrack_utils/ftrack_event_handler.py index 7011a57ed7..9e43e3e3bb 100644 --- a/pype/ftrack/events/ftrack_event_handler.py +++ b/pype/ftrack/ftrack_utils/ftrack_event_handler.py @@ -28,10 +28,12 @@ class BaseEvent(object): return self._session def register(self): - '''Registers the event, subscribing the the discover and launch topics.''' + '''Registers the event, subscribing the discover and launch topics.''' self.session.event_hub.subscribe('topic=ftrack.update', self._launch) - self.log.info("Event '{}' - Registered successfully".format(self.__class__.__name__)) + self.log.info("Event '{}' - Registered successfully".format( + self.__class__.__name__) + ) def _translate_event(self, session, event): '''Return *event* translated structure to be used with the API.''' @@ -43,7 +45,10 @@ class BaseEvent(object): continue _entities.append( ( - session.get(self._get_entity_type(entity), entity.get('entityId')) + session.get( + self._get_entity_type(entity), + entity.get('entityId') + ) ) ) @@ -110,7 +115,7 @@ class BaseEvent(object): def show_message(self, event, input_message, result=False): """ Shows message to user who triggered event - - event - just source of user id + - event - is just source of user id - input_message - message that is shown to user - result - changes color of message (based on ftrack settings) - True = Violet @@ -121,11 +126,13 @@ class BaseEvent(object): try: message = str(input_message) - except: + except Exception: return user_id = event['source']['user']['id'] - target = 'applicationId=ftrack.client.web and user.id="{0}"'.format(user_id) + target = ( + 'applicationId=ftrack.client.web and user.id="{0}"' + ).format(user_id) self.session.event_hub.publish( ftrack_api.event.base.Event( @@ -143,10 +150,13 @@ class BaseEvent(object): def show_interface(self, event, items, title=''): """ Shows interface to user who triggered event + - this interface is not interactive by default - 'items' must be list containing Ftrack interface items """ user_id = event['source']['user']['id'] - target = 'applicationId=ftrack.client.web and user.id="{0}"'.format(user_id) + target = ( + 'applicationId=ftrack.client.web and user.id="{0}"' + ).format(user_id) self.session.event_hub.publish( ftrack_api.event.base.Event( From 9572ac4fa69c1903e0f8a51b4ea88646ab87ee21 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Mon, 21 Jan 2019 13:56:39 +0100 Subject: [PATCH 07/27] filled __init__s so all utils are available from pype.ftrack --- pype/ftrack/__init__.py | 2 +- pype/ftrack/ftrack_utils/__init__.py | 3 +++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/pype/ftrack/__init__.py b/pype/ftrack/__init__.py index 8b13789179..d1e25ad0ef 100644 --- a/pype/ftrack/__init__.py +++ b/pype/ftrack/__init__.py @@ -1 +1 @@ - +from .ftrack_utils import * diff --git a/pype/ftrack/ftrack_utils/__init__.py b/pype/ftrack/ftrack_utils/__init__.py index fb09e548e6..77018a28a3 100644 --- a/pype/ftrack/ftrack_utils/__init__.py +++ b/pype/ftrack/ftrack_utils/__init__.py @@ -1,2 +1,5 @@ from .ftrack_utils import * from .avalon_sync import * +from .ftrack_app_handler import * +from .ftrack_event_handler import * +from .ftrack_action_handler import * From d402314eaffd5b8d8bc04616d67a98453309c431 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Mon, 21 Jan 2019 14:18:56 +0100 Subject: [PATCH 08/27] html login message is taken from html file in res --- pype/ftrack/login_tools.py | 65 ++++++++++++--------------------- res/ftrack/sign_in_message.html | 32 ++++++++++++++++ 2 files changed, 55 insertions(+), 42 deletions(-) create mode 100644 res/ftrack/sign_in_message.html diff --git a/pype/ftrack/login_tools.py b/pype/ftrack/login_tools.py index e38d3fa994..592ec152ee 100644 --- a/pype/ftrack/login_tools.py +++ b/pype/ftrack/login_tools.py @@ -1,10 +1,13 @@ from http.server import BaseHTTPRequestHandler, HTTPServer from urllib import parse +import os import webbrowser import functools +import pype +import inspect from app.vendor.Qt import QtCore -# class LoginServerHandler(BaseHTTPServer.BaseHTTPRequestHandler): + class LoginServerHandler(BaseHTTPRequestHandler): '''Login server handler.''' @@ -25,44 +28,21 @@ class LoginServerHandler(BaseHTTPRequestHandler): login_credentials = parse.parse_qs(query) api_user = login_credentials['api_user'][0] api_key = login_credentials['api_key'][0] - message = """ - - - -

Sign in to Ftrack was successful

-

- You signed in with username {0}. -

-

- You can close this window now. -

- - - """.format(api_user) + # get path to resources + path_items = os.path.dirname( + inspect.getfile(pype) + ).split(os.path.sep) + del path_items[-1] + path_items.extend(['res', 'ftrack', 'sign_in_message.html']) + message_filepath = os.path.sep.join(path_items) + message_file = open(message_filepath, 'r') + sign_in_message = message_file.read() + message_file.close() + # formatting html code for python + replacement = [('{', '{{'), ('}', '}}'), ('{{}}', '{}')] + for r in (replacement): + sign_in_message = sign_in_message.replace(*r) + message = sign_in_message.format(api_user) else: message = '

Failed to sign in

' @@ -70,7 +50,6 @@ class LoginServerHandler(BaseHTTPRequestHandler): self.end_headers() self.wfile.write(message.encode()) - if login_credentials: self.login_callback( api_user, @@ -84,7 +63,6 @@ class LoginServerThread(QtCore.QThread): # Login signal. loginSignal = QtCore.Signal(object, object, object) - def start(self, url): '''Start thread.''' self.url = url @@ -103,8 +81,11 @@ class LoginServerThread(QtCore.QThread): LoginServerHandler, self._handle_login ) ) + unformated_url = ( + '{0}/user/api_credentials?''redirect_url=http://localhost:{1}' + ) webbrowser.open_new_tab( - '{0}/user/api_credentials?redirect_url=http://localhost:{1}'.format( + unformated_url.format( self.url, self._server.server_port ) ) diff --git a/res/ftrack/sign_in_message.html b/res/ftrack/sign_in_message.html new file mode 100644 index 0000000000..8ee2828c26 --- /dev/null +++ b/res/ftrack/sign_in_message.html @@ -0,0 +1,32 @@ + + + +

Sign in to Ftrack was successful

+

+ You signed in with username {}. +

+

+ You can close this window now. +

+ + From 440f39d06f117d5d9af346f01ac5f8b8734ff413 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Mon, 21 Jan 2019 14:20:36 +0100 Subject: [PATCH 09/27] removed old unused code and few methods were renamed so their name better capture what they do --- pype/ftrack/ftrack_utils/avalon_sync.py | 4 +- pype/ftrack/ftrack_utils/ftrack_utils.py | 135 ++--------------------- 2 files changed, 14 insertions(+), 125 deletions(-) diff --git a/pype/ftrack/ftrack_utils/avalon_sync.py b/pype/ftrack/ftrack_utils/avalon_sync.py index 648c119b2f..931e12acf4 100644 --- a/pype/ftrack/ftrack_utils/avalon_sync.py +++ b/pype/ftrack/ftrack_utils/avalon_sync.py @@ -4,7 +4,7 @@ from pype import lib from pype.lib import get_avalon_database from avalon import schema from bson.objectid import ObjectId -from pype.ftrack.ftrack_utils import ftrack_utils +from . import ftrack_utils from avalon.vendor import jsonschema from app.api import Logger ValidationError = jsonschema.ValidationError @@ -50,7 +50,7 @@ def import_to_avalon( if entity_type in ['Project']: type = 'project' - config = ftrack_utils.get_config(entity) + config = ftrack_utils.get_project_config(entity) schema.validate(config) av_project_code = None diff --git a/pype/ftrack/ftrack_utils/ftrack_utils.py b/pype/ftrack/ftrack_utils/ftrack_utils.py index 4398beab8e..87618b6d84 100644 --- a/pype/ftrack/ftrack_utils/ftrack_utils.py +++ b/pype/ftrack/ftrack_utils/ftrack_utils.py @@ -1,18 +1,24 @@ import os import json -from pype import lib import avalon import avalon.api from avalon.vendor import toml, jsonschema from app.api import Logger +from pype import lib log = Logger.getLogger(__name__) -def get_config_data(): +def get_presets_path(): templates = os.environ['PYPE_STUDIO_TEMPLATES'] - path_items = [templates, 'presets', 'ftrack', 'ftrack_config.json'] + path_items = [templates, 'presets'] + filepath = os.path.sep.join(path_items) + return filepath + + +def get_config_data(): + path_items = [get_presets_path(), 'ftrack', 'ftrack_config.json'] filepath = os.path.sep.join(path_items) data = dict() try: @@ -66,7 +72,7 @@ def avalon_check_name(entity, inSchema=None): raise ValueError(msg.format(name)) -def get_apps(entity): +def get_project_apps(entity): """ Get apps from project Requirements: 'Entity' MUST be object of ftrack entity with entity_type 'Project' @@ -89,128 +95,11 @@ def get_apps(entity): return apps -def get_config(entity): +def get_project_config(entity): config = {} config['schema'] = lib.get_avalon_project_config_schema() config['tasks'] = [{'name': ''}] - config['apps'] = get_apps(entity) + config['apps'] = get_project_apps(entity) config['template'] = lib.get_avalon_project_template() return config - - -def get_context(entity): - parents = [] - item = entity - while True: - item = item['parent'] - if not item: - break - parents.append(item) - - ctx = collections.OrderedDict() - folder_counter = 0 - - entityDic = { - 'name': entity['name'], - 'id': entity['id'], - } - try: - entityDic['type'] = entity['type']['name'] - except Exception: - pass - - ctx[entity['object_type']['name']] = entityDic - - # add all parents to the context - for parent in parents: - tempdic = {} - if not parent.get('project_schema'): - tempdic = { - 'name': parent['name'], - 'id': parent['id'], - } - object_type = parent['object_type']['name'] - - if object_type == 'Folder': - object_type = object_type + str(folder_counter) - folder_counter += 1 - - ctx[object_type] = tempdic - - # add project to the context - project = entity['project'] - ctx['Project'] = { - 'name': project['full_name'], - 'code': project['name'], - 'id': project['id'], - 'root': project['root'] - } - - return ctx - - -def get_status_by_name(name): - statuses = ftrack.getTaskStatuses() - - result = None - for s in statuses: - if s.get('name').lower() == name.lower(): - result = s - - return result - - -def sort_types(types): - data = {} - for t in types: - data[t] = t.get('sort') - - data = sorted(data.items(), key=operator.itemgetter(1)) - results = [] - for item in data: - results.append(item[0]) - - return results - - -def get_next_task(task): - shot = task.getParent() - tasks = shot.getTasks() - - types_sorted = sort_types(ftrack.getTaskTypes()) - - next_types = None - for t in types_sorted: - if t.get('typeid') == task.get('typeid'): - try: - next_types = types_sorted[(types_sorted.index(t) + 1):] - except Exception: - pass - - for nt in next_types: - for t in tasks: - if nt.get('typeid') == t.get('typeid'): - return t - - return None - - -def get_latest_version(versions): - latestVersion = None - if len(versions) > 0: - versionNumber = 0 - for item in versions: - if item.get('version') > versionNumber: - versionNumber = item.getVersion() - latestVersion = item - return latestVersion - - -def get_thumbnail_recursive(task): - if task.get('thumbid'): - thumbid = task.get('thumbid') - return ftrack.Attachment(id=thumbid) - if not task.get('thumbid'): - parent = ftrack.Task(id=task.get('parent_id')) - return get_thumbnail_recursive(parent) From 485443189738992201420ab3184ca356b82b41d1 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Mon, 21 Jan 2019 14:20:56 +0100 Subject: [PATCH 10/27] sleep time changed back to 0.05 --- pype/ftrack/actions/action_Apps.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pype/ftrack/actions/action_Apps.py b/pype/ftrack/actions/action_Apps.py index dabba18a04..aebe745b6a 100644 --- a/pype/ftrack/actions/action_Apps.py +++ b/pype/ftrack/actions/action_Apps.py @@ -59,6 +59,6 @@ def register(session): for app in apps: try: registerApp(app, session) - time.sleep(0.03) + time.sleep(0.05) except Exception as e: log.warning("'{0}' - not proper App ({1})".format(app['name'], e)) From 18429e0380da12b96d98331345092a7de3e02258 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Mon, 21 Jan 2019 18:41:55 +0100 Subject: [PATCH 11/27] action_app renamed to application loader --- .../actions/{action_Apps.py => action_application_loader.py} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename pype/ftrack/actions/{action_Apps.py => action_application_loader.py} (100%) diff --git a/pype/ftrack/actions/action_Apps.py b/pype/ftrack/actions/action_application_loader.py similarity index 100% rename from pype/ftrack/actions/action_Apps.py rename to pype/ftrack/actions/action_application_loader.py From 4be34096d4ba16eedb0b862727061e08788f7132 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Mon, 21 Jan 2019 18:44:40 +0100 Subject: [PATCH 12/27] action create folders should be allright now --- pype/ftrack/actions/action_create_folders.py | 184 ++++++++++++++ .../old/action_folders_create_lucidity.py | 228 ------------------ 2 files changed, 184 insertions(+), 228 deletions(-) create mode 100644 pype/ftrack/actions/action_create_folders.py delete mode 100644 pype/ftrack/actions/old/action_folders_create_lucidity.py diff --git a/pype/ftrack/actions/action_create_folders.py b/pype/ftrack/actions/action_create_folders.py new file mode 100644 index 0000000000..16c7dd2dde --- /dev/null +++ b/pype/ftrack/actions/action_create_folders.py @@ -0,0 +1,184 @@ +import logging +import os +import argparse +import sys +import errno + +import ftrack_api +from pype.ftrack import BaseAction +import json +from pype import api as pype + + +class CreateFolders(BaseAction): + + '''Custom action.''' + + #: Action identifier. + identifier = 'create.folders' + + #: Action label. + label = 'Create Folders' + + #: Action Icon. + icon = ( + 'https://cdn1.iconfinder.com/data/icons/hawcons/32/' + '698620-icon-105-folder-add-512.png' + ) + + def discover(self, session, entities, event): + ''' Validation ''' + + # if (len(entities) == 0 or entities[0].entity_type not in + # ['Episode', 'Sequence', 'Shot', 'Folder', 'Asset Build']): + # return False + + return True + + def getShotAsset(self, entity): + if entity not in self.importable: + if entity['object_type']['name'] != 'Task': + self.importable.add(entity) + + if entity['children']: + children = entity['children'] + for child in children: + self.getShotAsset(child) + + def launch(self, session, entities, event): + '''Callback method for custom action.''' + + ####################################################################### + + # JOB SETTINGS + userId = event['source']['user']['id'] + user = session.query('User where id is ' + userId).one() + + job = session.create('Job', { + 'user': user, + 'status': 'running', + 'data': json.dumps({ + 'description': 'Creating Folders.' + }) + }) + + try: + self.importable = set([]) + # self.importable = [] + + self.Anatomy = pype.Anatomy + + project = entities[0]['project'] + + paths_collected = set([]) + + # get all child entities separately/unique + for entity in entities: + self.getShotAsset(entity) + + for ent in self.importable: + self.log.info("{}".format(ent['name'])) + + for entity in self.importable: + print(entity['name']) + + anatomy = pype.Anatomy + parents = entity['link'] + + hierarchy_names = [] + for p in parents[1:-1]: + hierarchy_names.append(p['name']) + + if hierarchy_names: + # hierarchy = os.path.sep.join(hierarchy) + hierarchy = os.path.join(*hierarchy_names) + + template_data = {"project": {"name": project['full_name'], + "code": project['name']}, + "asset": entity['name'], + "hierarchy": hierarchy} + + for task in entity['children']: + if task['object_type']['name'] == 'Task': + self.log.info('child: {}'.format(task['name'])) + template_data['task'] = task['name'] + anatomy_filled = anatomy.format(template_data) + paths_collected.add(anatomy_filled.work.folder) + paths_collected.add(anatomy_filled.publish.folder) + + for path in paths_collected: + self.log.info(path) + try: + os.makedirs(path) + except OSError as error: + if error.errno != errno.EEXIST: + raise + + job['status'] = 'done' + session.commit() + + except ValueError as ve: + job['status'] = 'failed' + session.commit() + message = str(ve) + self.log.error('Error during syncToAvalon: {}'.format(message)) + + except Exception: + job['status'] = 'failed' + session.commit() + + ####################################################################### + + return { + 'success': True, + 'message': 'Created Folders Successfully!' + } + + +def register(session, **kw): + '''Register plugin. Called when used as an plugin.''' + + if not isinstance(session, ftrack_api.session.Session): + return + + action_handler = CreateFolders(session) + action_handler.register() + + +def main(arguments=None): + '''Set up logging and register action.''' + if arguments is None: + arguments = [] + + parser = argparse.ArgumentParser() + # Allow setting of logging level from arguments. + loggingLevels = {} + for level in ( + logging.NOTSET, logging.DEBUG, logging.INFO, logging.WARNING, + logging.ERROR, logging.CRITICAL + ): + loggingLevels[logging.getLevelName(level).lower()] = level + + parser.add_argument( + '-v', '--verbosity', + help='Set the logging output verbosity.', + choices=loggingLevels.keys(), + default='info' + ) + namespace = parser.parse_args(arguments) + + # Set up basic logging + logging.basicConfig(level=loggingLevels[namespace.verbosity]) + + session = ftrack_api.Session() + register(session) + + # Wait for events + logging.info( + 'Registered actions and listening for events. Use Ctrl-C to abort.' + ) + session.event_hub.wait() + + +if __name__ == '__main__': + raise SystemExit(main(sys.argv[1:])) diff --git a/pype/ftrack/actions/old/action_folders_create_lucidity.py b/pype/ftrack/actions/old/action_folders_create_lucidity.py deleted file mode 100644 index 76864e5983..0000000000 --- a/pype/ftrack/actions/old/action_folders_create_lucidity.py +++ /dev/null @@ -1,228 +0,0 @@ -import logging -import os -import getpass -import argparse -import errno -import sys -import threading -import ftrack - -PLUGIN_DIRECTORY = os.path.abspath( - os.path.join(os.path.dirname(__file__), '..')) - -if PLUGIN_DIRECTORY not in sys.path: - sys.path.append(PLUGIN_DIRECTORY) - -import ft_utils - - -def async(fn): - '''Run *fn* asynchronously.''' - def wrapper(*args, **kwargs): - thread = threading.Thread(target=fn, args=args, kwargs=kwargs) - thread.start() - return wrapper - - -class CreateFolders(ftrack.Action): - - '''Custom action.''' - - #: Action identifier. - identifier = 'create.folders' - - #: Action label. - label = 'Create Folders' - - #: Action Icon. - icon = 'https://cdn1.iconfinder.com/data/icons/rcons-folder-action/32/folder_add-512.png' - - def __init__(self): - '''Initialise action handler.''' - self.logger = logging.getLogger( - __name__ + '.' + self.__class__.__name__ - ) - - def register(self): - '''Register action.''' - ftrack.EVENT_HUB.subscribe( - 'topic=ftrack.action.discover and source.user.username={0}'.format( - getpass.getuser() - ), - self.discover - ) - - ftrack.EVENT_HUB.subscribe( - 'topic=ftrack.action.launch and source.user.username={0} ' - 'and data.actionIdentifier={1}'.format( - getpass.getuser(), self.identifier - ), - self.launch - ) - - @async - def createFoldersFromEntity(self, entity): - '''Generate folder structure from *entity*. - - Entity is assumed to be either a project, episode, sequence or shot. - - ''' - - root = entity.getProject().getRoot() - - self.logger.info(root) - - if entity.getObjectType() in ( - 'Episode', 'Sequence', 'Folder', 'Shot'): - objects = entity.getChildren(objectType='Shot', depth=None) - objects.append(entity) - else: - objects = entity.getChildren(depth=None) - - for obj in objects: - - tasks = obj.getTasks() - paths_collected = set([]) - if obj.getObjectType() in ( - 'Episode', 'Sequence', 'Shot', 'Folder'): - task_mask = 'shot.task' - else: - task_mask = 'asset.task' - - self.logger.info(task_mask) - - for task in tasks: - self.logger.info(task) - paths = ft_utils.getAllPathsYaml(task) - self.logger.info(paths) - for path in paths: - if task_mask in path[1].name: - temppath = os.path.join( - root, path[0].lower().replace(" ", '_').replace('\'', '')) - paths_collected.add(temppath) - - for path in paths_collected: - self.logger.info(path) - try: - os.makedirs(path) - except OSError as error: - if error.errno != errno.EEXIST: - raise - - def validateSelection(self, selection): - '''Return true if the selection is valid. - - ''' - if len(selection) == 0: - return False - - entity = selection[0] - task = ftrack.Task(entity['entityId']) - - if task.getObjectType() not in ( - 'Episode', 'Sequence', 'Shot', 'Folder', 'Asset Build'): - return False - - return True - - def discover(self, event): - - selection = event['data'].get('selection', []) - - self.logger.info( - u'Discovering action with selection: {0}'.format(selection)) - - if not self.validateSelection(selection): - return - - return { - 'items': [{ - 'label': self.label, - 'actionIdentifier': self.identifier, - 'icon': self.icon, - }] - } - - def launch(self, event): - '''Callback method for custom action.''' - selection = event['data'].get('selection', []) - - ####################################################################### - job = ftrack.createJob( - description="Creating Folders", status="running") - try: - ftrack.EVENT_HUB.publishReply( - event, - data={ - 'success': True, - 'message': 'Folder Creation Job Started!' - } - ) - - for entity in selection: - if entity['entityType'] == 'task': - entity = ftrack.Task(entity['entityId']) - else: - entity = ftrack.Project(entity['entityId']) - - self.createFoldersFromEntity(entity) - # inform the user that the job is done - job.setStatus('done') - except: - job.setStatus('failed') - raise - - ####################################################################### - - return { - 'success': True, - 'message': 'Created Folders Successfully!' - } - - -def register(registry, **kw): - '''Register hooks.''' - if registry is not ftrack.EVENT_HANDLERS: - # Exit to avoid registering this plugin again. - return - logging.basicConfig(level=logging.DEBUG) - action = CreateFolders() - action.register() - - -def main(arguments=None): - '''Create folders action.''' - if arguments is None: - arguments = [] - - parser = argparse.ArgumentParser() - - # Allow setting of logging level from arguments. - loggingLevels = {} - for level in ( - logging.NOTSET, logging.DEBUG, logging.INFO, logging.WARNING, - logging.ERROR, logging.CRITICAL - ): - loggingLevels[logging.getLevelName(level).lower()] = level - - parser.add_argument( - '-v', '--verbosity', - help='Set the logging output verbosity.', - choices=loggingLevels.keys(), - default='info' - ) - namespace = parser.parse_args(arguments) - - '''Register action and listen for events.''' - logging.basicConfig(level=loggingLevels[namespace.verbosity]) - - # Subscribe to action. - ftrack.setup() - action = CreateFolders() - action.register() - - ftrack.EVENT_HUB.wait() - - -if __name__ == '__main__': - raise SystemExit(main(sys.argv[1:])) From 198c190564cea9c12115bc77fd5c6b0f8c39c608 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Mon, 21 Jan 2019 18:45:27 +0100 Subject: [PATCH 13/27] priority is set directly to action, test action is by default ignored --- pype/ftrack/actions/action_test.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/pype/ftrack/actions/action_test.py b/pype/ftrack/actions/action_test.py index 740f6ca223..31bcd4f518 100644 --- a/pype/ftrack/actions/action_test.py +++ b/pype/ftrack/actions/action_test.py @@ -13,6 +13,9 @@ from pype.ftrack import BaseAction from avalon import io, inventory, schema +ignore_me = True + + class TestAction(BaseAction): '''Edit meta data action.''' @@ -22,6 +25,8 @@ class TestAction(BaseAction): label = 'Test action' #: Action description. description = 'Test action' + #: priority + priority = 10000 def discover(self, session, entities, event): ''' Validation ''' @@ -38,7 +43,7 @@ class TestAction(BaseAction): return discover def launch(self, session, entities, event): - entity = entities[0] + self.log.info(event) return True @@ -50,7 +55,7 @@ def register(session, **kw): return action_handler = TestAction(session) - action_handler.register(10000) + action_handler.register() def main(arguments=None): From d74e79d0878e0675bcd5fd591e9b56691d832109 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Mon, 21 Jan 2019 18:46:02 +0100 Subject: [PATCH 14/27] priority is set directly to action/event, test action/event is by default ignored! --- pype/ftrack/actions/action_test.py | 9 +++++++-- pype/ftrack/events/event_test.py | 10 ++++++++-- 2 files changed, 15 insertions(+), 4 deletions(-) diff --git a/pype/ftrack/actions/action_test.py b/pype/ftrack/actions/action_test.py index 740f6ca223..31bcd4f518 100644 --- a/pype/ftrack/actions/action_test.py +++ b/pype/ftrack/actions/action_test.py @@ -13,6 +13,9 @@ from pype.ftrack import BaseAction from avalon import io, inventory, schema +ignore_me = True + + class TestAction(BaseAction): '''Edit meta data action.''' @@ -22,6 +25,8 @@ class TestAction(BaseAction): label = 'Test action' #: Action description. description = 'Test action' + #: priority + priority = 10000 def discover(self, session, entities, event): ''' Validation ''' @@ -38,7 +43,7 @@ class TestAction(BaseAction): return discover def launch(self, session, entities, event): - entity = entities[0] + self.log.info(event) return True @@ -50,7 +55,7 @@ def register(session, **kw): return action_handler = TestAction(session) - action_handler.register(10000) + action_handler.register() def main(arguments=None): diff --git a/pype/ftrack/events/event_test.py b/pype/ftrack/events/event_test.py index 128e1ad197..43c805119e 100644 --- a/pype/ftrack/events/event_test.py +++ b/pype/ftrack/events/event_test.py @@ -2,16 +2,22 @@ import os import sys import re import ftrack_api -from ftrack_event_handler import BaseEvent +from pype.ftrack import BaseEvent from app import api + +ignore_me = True + + class Test_Event(BaseEvent): + priority = 10000 + def launch(self, session, entities, event): '''just a testing event''' - # self.log.info(event) + self.log.info(event) return True From 895af6035514f70f528a84607d0c8ee2232dc6af Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Mon, 21 Jan 2019 18:47:30 +0100 Subject: [PATCH 15/27] djvwie action inheritate from base handler, formatted to flake8 --- .../actions/{djvview.py => action_djvview.py} | 29 +++++++++---------- 1 file changed, 14 insertions(+), 15 deletions(-) rename pype/ftrack/actions/{djvview.py => action_djvview.py} (95%) diff --git a/pype/ftrack/actions/djvview.py b/pype/ftrack/actions/action_djvview.py similarity index 95% rename from pype/ftrack/actions/djvview.py rename to pype/ftrack/actions/action_djvview.py index 662b26cfd4..0be3624f2d 100644 --- a/pype/ftrack/actions/djvview.py +++ b/pype/ftrack/actions/action_djvview.py @@ -5,27 +5,25 @@ import os import re from operator import itemgetter import ftrack_api -from app.api import Logger +from pype.ftrack import BaseHandler -class DJVViewAction(object): +class DJVViewAction(BaseHandler): """Launch DJVView action.""" identifier = "djvview-launch-action" # label = "DJV View" # icon = "http://a.fsdn.com/allura/p/djv/icon" + type = 'Application' def __init__(self, session): '''Expects a ftrack_api.Session instance''' - - self.log = Logger.getLogger(self.__class__.__name__) + super().__init__(session) if self.identifier is None: raise ValueError( 'Action missing identifier.' ) - self.session = session - def is_valid_selection(self, event): selection = event["data"].get("selection", []) @@ -87,11 +85,6 @@ class DJVViewAction(object): ), self.launch ) - self.log.info( - "Action '{}' - Registered successfully".format( - self.__class__.__name__ - ) - ) def get_applications(self): applications = [] @@ -346,15 +339,21 @@ class DJVViewAction(object): try: # TODO This is proper way to get filepath!!! # THIS WON'T WORK RIGHT NOW - location = component['component_locations'][0]['location'] + location = component[ + 'component_locations' + ][0]['location'] file_path = location.get_filesystem_path(component) # if component.isSequence(): # if component.getMembers(): - # frame = int(component.getMembers()[0].getName()) + # frame = int( + # component.getMembers()[0].getName() + # ) # file_path = file_path % frame except Exception: # This works but is NOT proper way - file_path = component['component_locations'][0]['resource_identifier'] + file_path = component[ + 'component_locations' + ][0]['resource_identifier'] event["data"]["items"].append( {"label": label, "value": file_path} @@ -382,7 +381,7 @@ class DJVViewAction(object): } -def register(session, **kw): +def register(session): """Register hooks.""" if not isinstance(session, ftrack_api.session.Session): return From 7c73d39a904fc0308f3e0aebfd4f080970ff3373 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Mon, 21 Jan 2019 18:47:51 +0100 Subject: [PATCH 16/27] radio button remastered, need testing --- pype/ftrack/events/event_radio_buttons.py | 40 +++++++++++++++++++++++ 1 file changed, 40 insertions(+) create mode 100644 pype/ftrack/events/event_radio_buttons.py diff --git a/pype/ftrack/events/event_radio_buttons.py b/pype/ftrack/events/event_radio_buttons.py new file mode 100644 index 0000000000..6d06326365 --- /dev/null +++ b/pype/ftrack/events/event_radio_buttons.py @@ -0,0 +1,40 @@ +import ftrack_api +from pype.ftrack import BaseEvent + + +class Radio_buttons(BaseEvent): + + def launch(self, session, entities, event): + '''Provides a readio button behaviour to any bolean attribute in + radio_button group.''' + + # start of event procedure ---------------------------------- + for entity in event['data'].get('entities', []): + + if entity['entityType'] == 'assetversion': + + query = 'CustomAttributeGroup where name is "radio_button"' + group = session.query(query).one() + radio_buttons = [] + for g in group['custom_attribute_configurations']: + radio_buttons.append(g['key']) + + for key in entity['keys']: + if (key in radio_buttons and entity['changes'] is not None): + if entity['changes'][key]['new'] == '1': + version = session.get('AssetVersion', + entity['entityId']) + asset = session.get('Asset', entity['parentId']) + for v in asset['versions']: + if version is not v: + v['custom_attributes'][key] = 0 + + session.commit() + + +def register(session): + '''Register plugin. Called when used as an plugin.''' + if not isinstance(session, ftrack_api.session.Session): + return + + Radio_buttons(session).register() From 381cdf5dac7dbe5007652196c6f9cad4fa4d9b32 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Mon, 21 Jan 2019 18:48:50 +0100 Subject: [PATCH 17/27] removed old events --- .../events/old/file_version_statuses.py | 38 ------------------ pype/ftrack/events/old/radio_buttons.py | 39 ------------------- 2 files changed, 77 deletions(-) delete mode 100644 pype/ftrack/events/old/file_version_statuses.py delete mode 100644 pype/ftrack/events/old/radio_buttons.py diff --git a/pype/ftrack/events/old/file_version_statuses.py b/pype/ftrack/events/old/file_version_statuses.py deleted file mode 100644 index 0f8ce11d25..0000000000 --- a/pype/ftrack/events/old/file_version_statuses.py +++ /dev/null @@ -1,38 +0,0 @@ -# import ftrack_api as local session -import ftrack_api -from utils import print_entity_head -# -session = ftrack_api.Session() - -# ---------------------------------- - - -def file_version_statuses(event): - '''Set new version status to data if version matches given types''' - - # start of event procedure ---------------------------------- - for entity in event['data'].get('entities', []): - - # Filter to new assetversions - if (entity['entityType'] == 'assetversion' - and entity['action'] == 'add'): - - print "\n\nevent script: {}".format(__file__) - print_entity_head.print_entity_head(entity, session) - - version = session.get('AssetVersion', entity['entityId']) - asset_type = version['asset']['type']['name'] - file_status = session.query( - 'Status where name is "{}"'.format('data')).one() - - # Setting task status - try: - if asset_type.lower() in ['cam', 'cache', 'rig', 'scene']: - version['status'] = file_status - except Exception as e: - print '!!! status couldnt be set [ {} ]'.format(e) - else: - print '>>> updated to [ {} ]'.format(file_status['name']) - - session.commit() - # end of event procedure ---------------------------------- diff --git a/pype/ftrack/events/old/radio_buttons.py b/pype/ftrack/events/old/radio_buttons.py deleted file mode 100644 index 0b7aac952a..0000000000 --- a/pype/ftrack/events/old/radio_buttons.py +++ /dev/null @@ -1,39 +0,0 @@ -# import ftrack_api as local session -import ftrack_api -from utils import print_entity_head -# -session = ftrack_api.Session() - -# ---------------------------------- - - -def radio_buttons(event): - '''Provides a readio button behaviour to any bolean attribute in - radio_button group.''' - - # start of event procedure ---------------------------------- - for entity in event['data'].get('entities', []): - - if entity['entityType'] == 'assetversion': - - print "\n\nevent script: {}".format(__file__) - print_entity_head.print_entity_head(entity, session) - - group = session.query( - 'CustomAttributeGroup where name is "radio_button"').one() - radio_buttons = [] - for g in group['custom_attribute_configurations']: - radio_buttons.append(g['key']) - - for key in entity['keys']: - if (key in radio_buttons and entity['changes'] is not None): - if entity['changes'][key]['new'] == '1': - version = session.get('AssetVersion', - entity['entityId']) - asset = session.get('Asset', entity['parentId']) - for v in asset['versions']: - if version is not v: - v['custom_attributes'][key] = 0 - - session.commit() - # end of event procedure ---------------------------------- From bef2f34d4a542cb5c77e89c615d5b63972468f82 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Mon, 21 Jan 2019 18:50:59 +0100 Subject: [PATCH 18/27] added event_ to names, formatting changed to flake8 --- ...t_task_update.py => event_next_task_update.py} | 15 +++++++++------ ...nail_updates.py => event_thumbnail_updates.py} | 2 +- ...tuses.py => event_version_to_task_statuses.py} | 11 ++++++----- 3 files changed, 16 insertions(+), 12 deletions(-) rename pype/ftrack/events/{next_task_update.py => event_next_task_update.py} (82%) rename pype/ftrack/events/{thumbnail_updates.py => event_thumbnail_updates.py} (97%) rename pype/ftrack/events/{version_to_task_statuses.py => event_version_to_task_statuses.py} (87%) diff --git a/pype/ftrack/events/next_task_update.py b/pype/ftrack/events/event_next_task_update.py similarity index 82% rename from pype/ftrack/events/next_task_update.py rename to pype/ftrack/events/event_next_task_update.py index 478003e61a..3391b3516f 100644 --- a/pype/ftrack/events/next_task_update.py +++ b/pype/ftrack/events/event_next_task_update.py @@ -1,5 +1,5 @@ import ftrack_api -from ftrack_event_handler import BaseEvent +from pype.ftrack import BaseEvent import operator @@ -64,14 +64,17 @@ class NextTaskUpdate(BaseEvent): # Setting next task status try: - status_to_set = session.query( - 'Status where name is "{}"'.format('Ready')).one() + query = 'Status where name is "{}"'.format('Ready') + status_to_set = session.query(query).one() next_task['status'] = status_to_set except Exception as e: - self.log.warning('!!! [ {} ] status couldnt be set: [ {} ]'.format( - path, e)) + self.log.warning(( + '!!! [ {} ] status couldnt be set: [ {} ]' + ).format(path, e)) else: - self.log.info('>>> [ {} ] updated to [ Ready ]'.format(path)) + self.log.info(( + '>>> [ {} ] updated to [ Ready ]' + ).format(path)) session.commit() diff --git a/pype/ftrack/events/thumbnail_updates.py b/pype/ftrack/events/event_thumbnail_updates.py similarity index 97% rename from pype/ftrack/events/thumbnail_updates.py rename to pype/ftrack/events/event_thumbnail_updates.py index 38e5597513..b6e0b12d56 100644 --- a/pype/ftrack/events/thumbnail_updates.py +++ b/pype/ftrack/events/event_thumbnail_updates.py @@ -1,5 +1,5 @@ import ftrack_api -from ftrack_event_handler import BaseEvent +from pype.ftrack import BaseEvent class ThumbnailEvents(BaseEvent): diff --git a/pype/ftrack/events/version_to_task_statuses.py b/pype/ftrack/events/event_version_to_task_statuses.py similarity index 87% rename from pype/ftrack/events/version_to_task_statuses.py rename to pype/ftrack/events/event_version_to_task_statuses.py index 2f776cf184..e91664a6fe 100644 --- a/pype/ftrack/events/version_to_task_statuses.py +++ b/pype/ftrack/events/event_version_to_task_statuses.py @@ -1,5 +1,5 @@ import ftrack_api -from ftrack_event_handler import BaseEvent +from pype.ftrack import BaseEvent class VersionToTaskStatus(BaseEvent): @@ -15,8 +15,9 @@ class VersionToTaskStatus(BaseEvent): 'statusid' in entity['keys']): version = session.get('AssetVersion', entity['entityId']) - version_status = session.get('Status', - entity['changes']['statusid']['new']) + version_status = session.get( + 'Status', entity['changes']['statusid']['new'] + ) task_status = version_status task = version['task'] self.log.info('>>> version status: [ {} ]'.format( @@ -34,8 +35,8 @@ class VersionToTaskStatus(BaseEvent): '>>> status to set: [ {} ]'.format(status_to_set)) if status_to_set is not None: - task_status = session.query( - 'Status where name is "{}"'.format(status_to_set)).one() + query = 'Status where name is "{}"'.format(status_to_set) + task_status = session.query(query).one() # Proceed if the task status was set if task_status: From f9db01775b06a8cf6865e4fd0861689b5ee40e46 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Mon, 21 Jan 2019 18:52:10 +0100 Subject: [PATCH 19/27] priority added directly to Class, removed register log --- pype/ftrack/actions/action_sync_to_avalon_local.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/pype/ftrack/actions/action_sync_to_avalon_local.py b/pype/ftrack/actions/action_sync_to_avalon_local.py index 35a3e333b1..686bf2361a 100644 --- a/pype/ftrack/actions/action_sync_to_avalon_local.py +++ b/pype/ftrack/actions/action_sync_to_avalon_local.py @@ -56,6 +56,8 @@ class SyncToAvalon(BaseAction): 'https://cdn1.iconfinder.com/data/icons/hawcons/32/' '699650-icon-92-inbox-download-512.png' ) + #: Action priority + priority = 200 def __init__(self, session): super(SyncToAvalon, self).__init__(session) @@ -236,7 +238,7 @@ def register(session, **kw): return action_handler = SyncToAvalon(session) - action_handler.register(200) + action_handler.register() def main(arguments=None): From 6ba6e9b33d329b643f524107b2bb2882d655acda Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Mon, 21 Jan 2019 18:54:05 +0100 Subject: [PATCH 20/27] removed register log, flake8 formatting --- .../actions/action_create_cust_attrs.py | 88 +++++++++++++------ .../actions/action_delete_unpublished.py | 1 - pype/ftrack/events/action_sync_to_avalon.py | 8 +- pype/ftrack/events/event_sync_to_avalon.py | 4 +- 4 files changed, 61 insertions(+), 40 deletions(-) diff --git a/pype/ftrack/actions/action_create_cust_attrs.py b/pype/ftrack/actions/action_create_cust_attrs.py index ce3a6e7f84..9cf79daecf 100644 --- a/pype/ftrack/actions/action_create_cust_attrs.py +++ b/pype/ftrack/actions/action_create_cust_attrs.py @@ -6,7 +6,7 @@ import argparse import json import ftrack_api import arrow -from pype.ftrack import BaseAction +from pype.ftrack import BaseAction, get_ca_mongoid """ This action creates/updates custom attributes. @@ -170,13 +170,13 @@ class CustomAttributes(BaseAction): session.rollback() job['status'] = 'failed' session.commit() - self.log.error("Creating custom attributes failed ({})".format(e)) + self.log.error('Creating custom attributes failed ({})'.format(e)) return True def avalon_mongo_id_attributes(self, session): # Attribute Name and Label - cust_attr_name = 'avalon_mongo_id' + cust_attr_name = get_ca_mongoid() cust_attr_label = 'Avalon/Mongo Id' # Types that don't need object_type_id @@ -195,7 +195,7 @@ class CustomAttributes(BaseAction): for obj_type in all_obj_types: name = obj_type['name'] if " " in name: - name = name.replace(" ", "") + name = name.replace(' ', '') if obj_type['name'] not in self.object_type_ids: self.object_type_ids[name] = obj_type['id'] @@ -204,7 +204,7 @@ class CustomAttributes(BaseAction): filtered_types_id.add(obj_type['id']) # Set security roles for attribute - role_list = ["API", "Administrator"] + role_list = ['API', 'Administrator'] roles = self.get_security_role(role_list) # Set Text type of Attribute custom_attribute_type = self.get_type('text') @@ -235,7 +235,10 @@ class CustomAttributes(BaseAction): with open(self.filepath) as data_file: json_dict = json.load(data_file) except Exception as e: - msg = 'Loading "Custom attribute file" Failed. Please check log for more information' + msg = ( + 'Loading "Custom attribute file" Failed.' + ' Please check log for more information' + ) self.log.warning("{} - {}".format(msg, str(e))) self.show_message(event, msg) return @@ -254,7 +257,9 @@ class CustomAttributes(BaseAction): self.process_attribute(data) except CustAttrException as cae: - msg = 'Custom attribute error "{}" - {}'.format(cust_attr_name, str(cae)) + msg = 'Custom attribute error "{}" - {}'.format( + cust_attr_name, str(cae) + ) self.log.warning(msg) self.show_message(event, msg) @@ -264,16 +269,20 @@ class CustomAttributes(BaseAction): existing_atr = self.session.query('CustomAttributeConfiguration').all() matching = [] for attr in existing_atr: - if (attr['key'] != data['key'] or - attr['type']['name'] != data['type']['name']): + if ( + attr['key'] != data['key'] or + attr['type']['name'] != data['type']['name'] + ): continue if 'is_hierarchical' in data: if data['is_hierarchical'] == attr['is_hierarchical']: matching.append(attr) elif 'object_type_id' in data: - if (attr['entity_type'] == data['entity_type'] and - attr['object_type_id'] == data['object_type_id']): + if ( + attr['entity_type'] == data['entity_type'] and + attr['object_type_id'] == data['object_type_id'] + ): matching.append(attr) else: if attr['entity_type'] == data['entity_type']: @@ -286,21 +295,29 @@ class CustomAttributes(BaseAction): elif len(matching) == 1: attr_update = matching[0] for key in data: - if key not in ['is_hierarchical','entity_type', 'object_type_id']: + if ( + key not in [ + 'is_hierarchical', 'entity_type', 'object_type_id' + ] + ): attr_update[key] = data[key] self.session.commit() else: - raise CustAttrException("Is duplicated") + raise CustAttrException('Is duplicated') def get_required(self, attr): output = {} for key in self.required_keys: if key not in attr: - raise CustAttrException("Key {} is required - please set".format(key)) + raise CustAttrException( + 'Key {} is required - please set'.format(key) + ) if attr['type'].lower() not in self.type_posibilities: - raise CustAttrException("Type {} is not valid".format(attr['type'])) + raise CustAttrException( + 'Type {} is not valid'.format(attr['type']) + ) type_name = attr['type'].lower() @@ -342,9 +359,9 @@ class CustomAttributes(BaseAction): def get_enumerator_config(self, attr): if 'config' not in attr: - raise CustAttrException("Missing config with data") + raise CustAttrException('Missing config with data') if 'data' not in attr['config']: - raise CustAttrException("Missing data in config") + raise CustAttrException('Missing data in config') data = [] for item in attr['config']['data']: @@ -361,7 +378,7 @@ class CustomAttributes(BaseAction): if isinstance(attr['config'][k], bool): multiSelect = attr['config'][k] else: - raise CustAttrException("Multiselect must be boolean") + raise CustAttrException('Multiselect must be boolean') break config = json.dumps({ @@ -397,7 +414,9 @@ class CustomAttributes(BaseAction): return group else: - raise CustAttrException("Found more than one group '{}'".format(group_name)) + raise CustAttrException( + 'Found more than one group "{}"'.format(group_name) + ) def get_role_ALL(self): role_name = 'ALL' @@ -434,8 +453,10 @@ class CustomAttributes(BaseAction): role = self.session.query(query).one() self.security_roles[role_name] = role roles.append(role) - except Exception as e: - raise CustAttrException("Securit role '{}' does not exist".format(role_name)) + except Exception: + raise CustAttrException( + 'Securit role "{}" does not exist'.format(role_name) + ) return roles @@ -454,12 +475,15 @@ class CustomAttributes(BaseAction): raise CustAttrException('{} boolean'.format(err_msg)) elif type == 'enumerator': if not isinstance(default, list): - raise CustAttrException('{} array with strings'.format(err_msg)) - # TODO check if multiSelect is available and if default is one of data menu + raise CustAttrException( + '{} array with strings'.format(err_msg) + ) + # TODO check if multiSelect is available + # and if default is one of data menu if not isinstance(default[0], str): raise CustAttrException('{} array of strings'.format(err_msg)) elif type == 'date': - date_items = default.split(" ") + date_items = default.split(' ') try: if len(date_items) == 1: default = arrow.get(default, 'YY.M.D') @@ -467,7 +491,7 @@ class CustomAttributes(BaseAction): default = arrow.get(default, 'YY.M.D H:m:s') else: raise Exception - except Exception as e: + except Exception: raise CustAttrException('Date is not in proper format') elif type == 'dynamic enumerator': raise CustAttrException('Dynamic enumerator can\'t have default') @@ -505,7 +529,9 @@ class CustomAttributes(BaseAction): def get_entity_type(self, attr): if 'is_hierarchical' in attr: if attr['is_hierarchical'] is True: - type = attr['entity_type'] if ('entity_type' in attr) else 'show' + type = 'show' + if 'entity_type' in attr: + type = attr['entity_type'] return { 'is_hierarchical': True, @@ -524,10 +550,14 @@ class CustomAttributes(BaseAction): object_type_name = attr['object_type'] if object_type_name not in self.object_type_ids: try: - query = 'ObjectType where name is "{}"'.format(object_type_name) + query = 'ObjectType where name is "{}"'.format( + object_type_name + ) object_type_id = self.session.query(query).one()['id'] - except Exception as e: - raise CustAttrException('Object type with name "{}" don\'t exist'.format(object_type_name)) + except Exception: + raise CustAttrException(( + 'Object type with name "{}" don\'t exist' + ).format(object_type_name)) self.object_type_ids[object_type_name] = object_type_id else: object_type_id = self.object_type_ids[object_type_name] diff --git a/pype/ftrack/actions/action_delete_unpublished.py b/pype/ftrack/actions/action_delete_unpublished.py index 41f95cfb70..9c7ae60f3b 100644 --- a/pype/ftrack/actions/action_delete_unpublished.py +++ b/pype/ftrack/actions/action_delete_unpublished.py @@ -1,7 +1,6 @@ import sys import argparse import logging -import getpass import ftrack_api from pype.ftrack import BaseAction diff --git a/pype/ftrack/events/action_sync_to_avalon.py b/pype/ftrack/events/action_sync_to_avalon.py index 0e55c1ed70..1cfd13e323 100644 --- a/pype/ftrack/events/action_sync_to_avalon.py +++ b/pype/ftrack/events/action_sync_to_avalon.py @@ -4,8 +4,7 @@ import argparse import logging import ftrack_api import json -from pype.ftrack import ftrack_utils -from pype.ftrack.actions.ftrack_action_handler import BaseAction +from pype.ftrack import ftrack_utils, BaseAction class Sync_To_Avalon(BaseAction): @@ -56,7 +55,6 @@ class Sync_To_Avalon(BaseAction): ) def register(self): - '''Registers the action, subscribing the the discover and launch topics.''' self.session.event_hub.subscribe( 'topic=ftrack.action.discover', self._discover @@ -68,10 +66,6 @@ class Sync_To_Avalon(BaseAction): ), self._launch ) - msg = ( - "Action '{}' - Registered successfully" - ).format(self.__class__.__name__) - self.log.info(msg) def discover(self, session, entities, event): ''' Validation ''' diff --git a/pype/ftrack/events/event_sync_to_avalon.py b/pype/ftrack/events/event_sync_to_avalon.py index 25b8be4359..e6c6e0ddc0 100644 --- a/pype/ftrack/events/event_sync_to_avalon.py +++ b/pype/ftrack/events/event_sync_to_avalon.py @@ -1,7 +1,5 @@ -import os import ftrack_api -from pype.ftrack import ftrack_utils -from ftrack_event_handler import BaseEvent +from pype.ftrack import ftrack_utils, BaseEvent class Sync_to_Avalon(BaseEvent): From 1f779f627b87ae52465db592fa75db5223a2a716 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Mon, 21 Jan 2019 19:02:40 +0100 Subject: [PATCH 21/27] same functionalities of hanlers were put to base handler --- .../ftrack_utils/ftrack_action_handler.py | 217 +----------- .../ftrack/ftrack_utils/ftrack_app_handler.py | 153 +-------- .../ftrack_utils/ftrack_base_handler.py | 312 ++++++++++++++++++ .../ftrack_utils/ftrack_event_handler.py | 150 ++------- 4 files changed, 351 insertions(+), 481 deletions(-) create mode 100644 pype/ftrack/ftrack_utils/ftrack_base_handler.py diff --git a/pype/ftrack/ftrack_utils/ftrack_action_handler.py b/pype/ftrack/ftrack_utils/ftrack_action_handler.py index 60a1a9ae0e..a02a4da5e5 100644 --- a/pype/ftrack/ftrack_utils/ftrack_action_handler.py +++ b/pype/ftrack/ftrack_utils/ftrack_action_handler.py @@ -1,10 +1,7 @@ -# :coding: utf-8 -# :copyright: Copyright (c) 2017 ftrack -import ftrack_api -from pype import api as pype +from .ftrack_base_handler import BaseHandler -class BaseAction(object): +class BaseAction(BaseHandler): '''Custom Action base class `label` a descriptive string identifing your action. @@ -22,11 +19,11 @@ class BaseAction(object): identifier = None description = None icon = None + type = 'Action' def __init__(self, session): '''Expects a ftrack_api.Session instance''' - - self.log = pype.Logger.getLogger(self.__class__.__name__) + super().__init__(session) if self.label is None: raise ValueError( @@ -38,17 +35,7 @@ class BaseAction(object): 'Action missing identifier.' ) - self._session = session - - @property - def session(self): - '''Return current session.''' - return self._session - - def reset_session(self): - self.session.reset() - - def register(self, priority=100): + def register(self): ''' Registers the action, subscribing the the discover and launch topics. - highest priority event will show last @@ -56,7 +43,9 @@ class BaseAction(object): self.session.event_hub.subscribe( 'topic=ftrack.action.discover and source.user.username={0}'.format( self.session.api_user - ), self._discover, priority=priority + ), + self._discover, + priority=self.priority ) launch_subscription = ( @@ -72,93 +61,6 @@ class BaseAction(object): self._launch ) - self.log.info("Action '{}' - Registered successfully".format( - self.__class__.__name__)) - - def _discover(self, event): - args = self._translate_event( - self.session, event - ) - - accepts = self.discover( - self.session, *args - ) - - if accepts: - self.log.debug(u'Discovering action with selection: {0}'.format( - args[1]['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 _translate_event(self, session, event): - '''Return *event* translated structure to be used with the API.''' - - _selection = event['data'].get('selection', []) - - _entities = list() - for entity in _selection: - _entities.append( - ( - session.get( - self._get_entity_type(entity), - entity.get('entityId') - ) - ) - ) - - return [ - _entities, - event - ] - - def _get_entity_type(self, entity): - '''Return translated entity type tht can be used with API.''' - # Get entity type and make sure it is lower cased. Most places except - # the component tab in the Sidebar will use lower case notation. - entity_type = entity.get('entityType').replace('_', '').lower() - - for schema in self.session.schemas: - alias_for = schema.get('alias_for') - - if ( - alias_for and isinstance(alias_for, str) and - alias_for.lower() == entity_type - ): - return schema['id'] - - for schema in self.session.schemas: - if schema['id'].lower() == entity_type: - return schema['id'] - - raise ValueError( - 'Unable to translate entity type: {0}.'.format(entity_type) - ) - def _launch(self, event): self.reset_session() args = self._translate_event( @@ -180,86 +82,6 @@ class BaseAction(object): self.session, response, *args ) - def launch(self, session, entities, event): - '''Callback method for the custom action. - - return either a bool ( True if successful or False if the action failed ) - or a dictionary with they keys `message` and `success`, the message should be a - string and will be displayed as feedback to the user, success should be a bool, - True if successful or False if the action failed. - - *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 - - ''' - raise NotImplementedError() - - def _interface(self, *args): - interface = self.interface(*args) - if interface: - if 'items' in interface: - return interface - - return { - 'items': 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 show_message(self, event, input_message, result=False): - """ - Shows message to user who triggered event - - event - just source of user id - - input_message - message that is shown to user - - result - changes color of message (based on ftrack settings) - - True = Violet - - False = Red - """ - if not isinstance(result, bool): - result = False - - try: - message = str(input_message) - except Exception: - return - - user_id = event['source']['user']['id'] - target = ( - 'applicationId=ftrack.client.web and user.id="{0}"' - ).format(user_id) - self.session.event_hub.publish( - ftrack_api.event.base.Event( - topic='ftrack.action.trigger-user-interface', - data=dict( - type='message', - success=result, - message=message - ), - target=target - ), - on_error='ignore' - ) - def _handle_result(self, session, result, entities, event): '''Validate the returned result from the action callback''' if isinstance(result, bool): @@ -293,26 +115,3 @@ class BaseAction(object): ) return result - - def show_interface(self, event, items, title=''): - """ - Shows interface to user who triggered event - - 'items' must be list containing Ftrack interface items - """ - user_id = event['source']['user']['id'] - target = ( - 'applicationId=ftrack.client.web and user.id="{0}"' - ).format(user_id) - - self.session.event_hub.publish( - ftrack_api.event.base.Event( - topic='ftrack.action.trigger-user-interface', - data=dict( - type='widget', - items=items, - title=title - ), - target=target - ), - on_error='ignore' - ) diff --git a/pype/ftrack/ftrack_utils/ftrack_app_handler.py b/pype/ftrack/ftrack_utils/ftrack_app_handler.py index a4977f34c5..402739799d 100644 --- a/pype/ftrack/ftrack_utils/ftrack_app_handler.py +++ b/pype/ftrack/ftrack_utils/ftrack_app_handler.py @@ -8,9 +8,10 @@ import acre from . import ftrack_utils from pype import api as pype from pype import lib as pypelib +from .ftrack_base_handler import BaseHandler -class AppAction(object): +class AppAction(BaseHandler): '''Custom Action base class