diff --git a/pype/ftrack/__init__.py b/pype/ftrack/__init__.py index 8b13789179..1224715000 100644 --- a/pype/ftrack/__init__.py +++ b/pype/ftrack/__init__.py @@ -1 +1 @@ - +from .lib import * diff --git a/pype/ftrack/actions/action_Apps.py b/pype/ftrack/actions/action_application_loader.py similarity index 97% rename from pype/ftrack/actions/action_Apps.py rename to pype/ftrack/actions/action_application_loader.py index fe7664470a..aebe745b6a 100644 --- a/pype/ftrack/actions/action_Apps.py +++ b/pype/ftrack/actions/action_application_loader.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 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..8e926fb313 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, get_ca_mongoid """ 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): ''' @@ -166,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 @@ -191,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'] @@ -200,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') @@ -231,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 @@ -250,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) @@ -260,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']: @@ -278,25 +291,40 @@ class CustomAttributes(BaseAction): if len(matching) == 0: self.session.create('CustomAttributeConfiguration', data) self.session.commit() + self.log.debug( + '{}: "{}" created'.format(self.label, data['label']) + ) 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.log.debug( + '{}: "{}" updated'.format(self.label, data['label']) + ) 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() @@ -338,9 +366,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']: @@ -357,7 +385,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({ @@ -393,7 +421,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' @@ -430,8 +460,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 @@ -450,12 +482,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') @@ -463,7 +498,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') @@ -501,7 +536,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, @@ -520,10 +557,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_folders_create_lucidity.py b/pype/ftrack/actions/action_create_folders.py similarity index 96% rename from pype/ftrack/actions/action_folders_create_lucidity.py rename to pype/ftrack/actions/action_create_folders.py index c1b9d9aba6..16c7dd2dde 100644 --- a/pype/ftrack/actions/action_folders_create_lucidity.py +++ b/pype/ftrack/actions/action_create_folders.py @@ -5,7 +5,7 @@ import sys import errno import ftrack_api -from ftrack_action_handler import BaseAction +from pype.ftrack import BaseAction import json from pype import api as pype @@ -21,7 +21,10 @@ class CreateFolders(BaseAction): label = 'Create Folders' #: Action Icon. - icon = 'https://cdn1.iconfinder.com/data/icons/hawcons/32/698620-icon-105-folder-add-512.png' + icon = ( + 'https://cdn1.iconfinder.com/data/icons/hawcons/32/' + '698620-icon-105-folder-add-512.png' + ) def discover(self, session, entities, event): ''' Validation ''' @@ -120,7 +123,7 @@ class CreateFolders(BaseAction): message = str(ve) self.log.error('Error during syncToAvalon: {}'.format(message)) - except Exception as e: + except Exception: job['status'] = 'failed' session.commit() diff --git a/pype/ftrack/actions/action_delete_unpublished.py b/pype/ftrack/actions/action_delete_unpublished.py index effa66072e..9c7ae60f3b 100644 --- a/pype/ftrack/actions/action_delete_unpublished.py +++ b/pype/ftrack/actions/action_delete_unpublished.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 VersionsCleanup(BaseAction): @@ -14,7 +13,6 @@ class VersionsCleanup(BaseAction): # Action label label = 'Versions cleanup' - def discover(self, session, entities, event): ''' Validation ''' @@ -34,13 +32,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/djvview.py b/pype/ftrack/actions/action_djvview.py similarity index 80% rename from pype/ftrack/actions/djvview.py rename to pype/ftrack/actions/action_djvview.py index 549a439955..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", []) @@ -75,15 +73,18 @@ 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 -----") def get_applications(self): applications = [] @@ -115,16 +116,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 +160,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 +177,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 +217,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 +243,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 +308,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 +318,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 @@ -317,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: + 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} @@ -353,7 +381,7 @@ class DJVViewAction(object): } -def register(session, **kw): +def register(session): """Register hooks.""" if not isinstance(session, ftrack_api.session.Session): return @@ -367,6 +395,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/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 { 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..97ab49a272 100644 --- a/pype/ftrack/actions/action_sync_to_avalon_local.py +++ b/pype/ftrack/actions/action_sync_to_avalon_local.py @@ -3,11 +3,9 @@ import sys import argparse import logging import json -import importlib import ftrack_api -from ftrack_action_handler import BaseAction -from pype.ftrack import ftrack_utils +from pype.ftrack import BaseAction, lib as ftracklib class SyncToAvalon(BaseAction): @@ -56,11 +54,12 @@ 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) # reload utils on initialize (in case of server restart) - importlib.reload(ftrack_utils) def discover(self, session, entities, event): ''' Validation ''' @@ -118,12 +117,12 @@ class SyncToAvalon(BaseAction): all_names = [] duplicates = [] - for e in self.importable: - ftrack_utils.avalon_check_name(e) - if e['name'] in all_names: + for entity in self.importable: + ftracklib.avalon_check_name(entity) + if entity['name'] in all_names: duplicates.append("'{}'".format(e['name'])) else: - all_names.append(e['name']) + all_names.append(entity['name']) if len(duplicates) > 0: raise ValueError( @@ -133,12 +132,12 @@ class SyncToAvalon(BaseAction): # ----- PROJECT ------ # store Ftrack project- self.importable[0] must be project entity!! ft_project = self.importable[0] - avalon_project = ftrack_utils.get_avalon_project(ft_project) - custom_attributes = ftrack_utils.get_avalon_attr(session) + avalon_project = ftracklib.get_avalon_project(ft_project) + custom_attributes = ftracklib.get_avalon_attr(session) # Import all entities to Avalon DB for entity in self.importable: - result = ftrack_utils.import_to_avalon( + result = ftracklib.import_to_avalon( session=session, entity=entity, ft_project=ft_project, @@ -177,18 +176,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 +197,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) @@ -235,7 +235,7 @@ def register(session, **kw): return action_handler = SyncToAvalon(session) - action_handler.register(200) + action_handler.register() def main(arguments=None): diff --git a/pype/ftrack/actions/action_test.py b/pype/ftrack/actions/action_test.py index f40807a420..31bcd4f518 100644 --- a/pype/ftrack/actions/action_test.py +++ b/pype/ftrack/actions/action_test.py @@ -9,10 +9,13 @@ import json import re import ftrack_api -from ftrack_action_handler import BaseAction +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/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/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) diff --git a/pype/ftrack/actions/ftrack_action_handler.py b/pype/ftrack/actions/ftrack_action_handler.py deleted file mode 100644 index 1b31f5e27d..0000000000 --- a/pype/ftrack/actions/ftrack_action_handler.py +++ /dev/null @@ -1,783 +0,0 @@ -# :coding: utf-8 -# :copyright: Copyright (c) 2017 ftrack -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 pype import api as pype - - -ignore_me = True - - -class AppAction(object): - '''Custom Action base class - -