diff --git a/pype/ftrack/actions/action_killRunningJobs.py b/pype/ftrack/actions/action_killRunningJobs.py new file mode 100644 index 0000000000..585c4c02ba --- /dev/null +++ b/pype/ftrack/actions/action_killRunningJobs.py @@ -0,0 +1,127 @@ +# :coding: utf-8 +# :copyright: Copyright (c) 2017 ftrack +import sys +import argparse +import logging +import collections +import os +import datetime +import json +import ftrack_api +from ftrack_action_handler.action import BaseAction + +from avalon import io, inventory, schema +from avalon.vendor import toml + + +class JobKiller(BaseAction): + '''Edit meta data action.''' + + #: Action identifier. + identifier = 'job.kill' + + #: Action label. + label = 'Job Killer' + + #: Action description. + description = 'Killing all running jobs younger than day' + + def validate_selection(self, session, entities): + '''Return if *entities* is a valid selection.''' + # if (len(entities) != 1): + # # If entities contains more than one item return early since + # # metadata cannot be edited for several entites at the same time. + # return False + pass + # entity_type, entity_id = entities[0] + # if ( + # entity_type not in session.types + # ): + # # Return False if the target entity does not have a metadata + # # attribute. + # return False + + return True + + def discover(self, session, entities, event): + '''Return True if action is valid.''' + + self.logger.info('Got selection: {0}'.format(entities)) + return self.validate_selection(session, entities) + + def launch(self, session, entities, event): + """ JOB SETTING """ + + yesterday = datetime.date.today() - datetime.timedelta(days=1) + + jobs = session.query( + 'select id, status from Job ' + 'where status in ("queued", "running") and created_at > {0}'.format(yesterday) + ) + + # Update all the queried jobs, setting the status to failed. + for job in jobs: + print(job['created_at']) + print('Changing Job ({}) status: {} -> failed'.format(job['id'], job['status'])) + job['status'] = 'failed' + + session.commit() + + print('Complete') + return True + + +def register(session, **kw): + '''Register plugin. Called when used as an plugin.''' + + # Validate that session is an instance of ftrack_api.Session. If not, + # assume that register is being called from an old or incompatible API and + # return without doing anything. + if not isinstance(session, ftrack_api.session.Session): + return + + action_handler = JobKiller(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( + server_url="https://pype.ftrackapp.com", + api_key="4e01eda0-24b3-4451-8e01-70edc03286be", + api_user="jakub.trllo" + ) + 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/action_syncToAvalon.py b/pype/ftrack/actions/action_syncToAvalon.py index 4e0b86c62b..0440d09f69 100644 --- a/pype/ftrack/actions/action_syncToAvalon.py +++ b/pype/ftrack/actions/action_syncToAvalon.py @@ -51,8 +51,9 @@ class SyncToAvalon(BaseAction): def importToAvalon(self, session, entity): eLinks = [] + custAttrName = 'avalon_mongo_id' # TODO read from file, which data are in scope??? - """ get needed info of entity and all parents """ + # get needed info of entity and all parents for e in entity['link']: tmp = session.get(e['type'], e['id']) if e['name'].find(" ") == -1: @@ -65,7 +66,7 @@ class SyncToAvalon(BaseAction): entityProj = session.get(eLinks[0]['type'], eLinks[0]['ftrackId']) - """ set AVALON_PROJECT env """ + # set AVALON_PROJECT env os.environ["AVALON_PROJECT"] = entityProj["full_name"] # get schema of project TODO read different schemas based on project type @@ -89,8 +90,10 @@ class SyncToAvalon(BaseAction): # Store project Id projectId = io.find_one({"type": "project", "name": entityProj["full_name"]})["_id"] + if custAttrName in entityProj['custom_attributes'] and entityProj['custom_attributes'][custAttrName] is '': + entityProj['custom_attributes'][custAttrName] = str(projectId) - # If entity is Project or is Silo kill action + # If entity is Project or Silo kill action if (len(eLinks) > 2) and not (eLinks[-1]['type'] in ['Project']): silo = eLinks[1] @@ -102,13 +105,12 @@ class SyncToAvalon(BaseAction): folderStruct = [] folderStruct.append(silo['name']) parentId = None - data = {'ftrackId': None, 'visualParent': parentId, 'parents': folderStruct, - 'entityType': None} + data = {'visualParent': parentId, 'parents': folderStruct, + 'ftrackId': None, 'entityType': None} for asset in assets: - data = {'ftrackId': asset['ftrackId'], 'visualParent': data['visualParent'], - 'parents': data['parents'], 'entityType': asset['type']} - if (io.find_one({'type': 'asset', 'name': asset['name']}) == None): + data.update({'ftrackId': asset['ftrackId'], 'entityType': asset['type']}) + if (io.find_one({'type': 'asset', 'name': asset['name']}) is None): inventory.create_asset(asset['name'], silo['name'], data, projectId) print("Asset "+asset['name']+" created") @@ -118,13 +120,18 @@ class SyncToAvalon(BaseAction): print("Asset "+asset["name"]+" already exist") parentId = io.find_one({'type': 'asset', 'name': asset['name']})['_id'] - data = {'visualParent': parentId, 'parents': folderStruct, } + + data.update({'visualParent': parentId, 'parents': folderStruct}) folderStruct.append(asset['name']) + # Set custom attribute to avalon/mongo id of entity (parentID is last) + if custAttrName in entity['custom_attributes'] and entity['custom_attributes'][custAttrName] is '': + entity['custom_attributes'][custAttrName] = str(parentId) + io.uninstall() def launch(self, session, entities, event): - """ JOB SETTING """ + # JOB SETTINGS userId = event['source']['user']['id'] user = session.query('User where id is ' + userId).one() @@ -134,7 +141,6 @@ class SyncToAvalon(BaseAction): 'data': json.dumps({ 'description': 'Synch Ftrack to Avalon.' }) - }) try: @@ -142,7 +148,7 @@ class SyncToAvalon(BaseAction): def getShotAsset(entity): if not (entity.entity_type in ['Task']): - if not (entity in importable): + if entity not in importable: importable.append(entity) if entity['children']: @@ -150,37 +156,12 @@ class SyncToAvalon(BaseAction): for child in childrens: getShotAsset(child) - """ Get all Shots and AssetBuild from entities """ + # get all entities separately for entity in entities: entity_type, entity_id = entity act_ent = session.get(entity_type, entity_id) getShotAsset(act_ent) - # _handle_result -> by to mel resit - # """ TODO Check if name of entities match REGEX""" - # for entity in importable: - # for e in entity['link']: - # item = { - # "silo": "silo", - # "parent": "parent", - # "type": "asset", - # "schema": "avalon-core:asset-2.0", - # "name": e['name'], - # "data": dict(), - # } - # try: - # schema.validate(item) - # except Exception as e: - # print(e) - # print(e['name']) - # ftrack.EVENT_HUB.publishReply( - # event, - # data={ - # 'success': False, - # 'message': 'Entity name contains invalid character!' - # } - # ) - for e in importable: self.importToAvalon(session, e) diff --git a/pype/ftrack/actions/test.py b/pype/ftrack/actions/test.py new file mode 100644 index 0000000000..c9db886006 --- /dev/null +++ b/pype/ftrack/actions/test.py @@ -0,0 +1,111 @@ +# :coding: utf-8 +# :copyright: Copyright (c) 2017 ftrack +import sys +import argparse +import logging +import collections +import os + +import json +import ftrack_api +from ftrack_action_handler.action import BaseAction + +from avalon import io, inventory, schema +from avalon.vendor import toml + + +class TestAction(BaseAction): + '''Edit meta data action.''' + + #: Action identifier. + identifier = 'test.action' + + #: Action label. + label = 'Test action' + + #: Action description. + description = 'Test action' + + def validate_selection(self, session, entities): + '''Return if *entities* is a valid selection.''' + # if (len(entities) != 1): + # # If entities contains more than one item return early since + # # metadata cannot be edited for several entites at the same time. + # return False + pass + # entity_type, entity_id = entities[0] + # if ( + # entity_type not in session.types + # ): + # # Return False if the target entity does not have a metadata + # # attribute. + # return False + + return True + + def discover(self, session, entities, event): + '''Return True if action is valid.''' + + self.logger.info('Got selection: {0}'.format(entities)) + return self.validate_selection(session, entities) + + def launch(self, session, entities, event): + """ JOB SETTING """ + + return True + + +def register(session, **kw): + '''Register plugin. Called when used as an plugin.''' + + # Validate that session is an instance of ftrack_api.Session. If not, + # assume that register is being called from an old or incompatible API and + # return without doing anything. + if not isinstance(session, ftrack_api.session.Session): + return + + action_handler = TestAction(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( + server_url="https://pype.ftrackapp.com", + api_key="4e01eda0-24b3-4451-8e01-70edc03286be", + api_user="jakub.trllo" + ) + 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.py b/pype/ftrack/ftrack_utils.py new file mode 100644 index 0000000000..9b2e122df9 --- /dev/null +++ b/pype/ftrack/ftrack_utils.py @@ -0,0 +1,126 @@ +# fttrack help functions + + +# import ftrack +import os +from pprint import * + + +def deleteAssetsForTask(taskId): + #taskId = os.environ['FTRACK_TASKID'] + task = ftrack.Task(taskId) + + taskAssets = task.getAssets() + print(taskAssets) + for a in taskAssets: + print(a.getName()) + a.delete() + #shot = task.getParent() + #shotAssets = shot.getAssets() + + +def deleteAssetsFromShotByName(shotId, assNm=None): + if not assNm: + return + + shot = ftrack.Task(shotId) + + shotAssets = shot.getAssets() + for a in shotAssets: + nm = a.getName() + if nm == assNm: + a.delete() + + +def killRunningTasks(tm=None): + import datetime + import ftrack_api + + session = ftrack_api.Session() + + # Query all jobs created prior to yesterday which has not yet been completed. + yesterday = datetime.date.today() - datetime.timedelta(days=1) + if tm: + yesterday = tm + print(yesterday) + jobs = session.query( + 'select id, status from Job ' + 'where status in ("queued", "running") and created_at > {0}'.format(yesterday) + ) + + # Update all the queried jobs, setting the status to failed. + for job in jobs: + print(job['created_at']) + print('Changing Job ({}) status: {} -> failed'.format(job['id'], job['status'])) + job['status'] = 'failed' + + session.commit() + + print('Complete') + + +def createCustomAttr(): + + import ftrack_api + + session = ftrack_api.Session() + objTypes = set() # for custom attribute creation + + # TODO get all entity types ---- NOT TASK + # objTypes.add(session.query('ObjectType where name is ' + entity.entity_type).one()) + + # Name & Label for export Avalon-mongo ID to Ftrack + allCustAttr = session.query('CustomAttributeConfiguration').all() + curCustAttr = [] + for ca in allCustAttr: + curCustAttr.append(ca['key']) + + custAttrName = 'avalon_mongo_id' + custAttrLabel = 'Avalon/Mongo Id' + custAttrType = session.query('CustomAttributeType where name is "text"').one() + # TODO WHICH SECURITY ROLE IS RIGHT + custAttrSecuRole = session.query('SecurityRole').all() + + for custAttrObjType in objTypes: + # custAttrObjType = session.query('ObjectType where name is ' + entity.entity_type).one() + # Create Custom attribute if not exists + if custAttrName not in curCustAttr: + session.create('CustomAttributeConfiguration', { + 'entity_type': 'task', + 'object_type_id': custAttrObjType['id'], + 'type': custAttrType, + 'label': custAttrLabel, + 'key': custAttrName, + 'default': '', + 'write_security_roles': custAttrSecuRole, + 'read_security_roles': custAttrSecuRole, + 'config': json.dumps({'markdown': False}), + }) + session.commit() + + +def checkRegex(): + # _handle_result -> would be solution? + # """ TODO Check if name of entities match REGEX""" + for entity in importable: + for e in entity['link']: + item = { + "silo": "silo", + "parent": "parent", + "type": "asset", + "schema": "avalon-core:asset-2.0", + "name": e['name'], + "data": dict(), + } + try: + schema.validate(item) + except Exception as e: + print(e) + print(e['name']) + ftrack.EVENT_HUB.publishReply( + event, + data={ + 'success': False, + 'message': 'Entity name contains invalid character!' + } + )