def discover(self, session, entities, event): """Return true if we can handle the selected entities. Args: session (ftrack_api.Session): Helps to query necessary data. entities (list): Object of selected entities. event (ftrack_api.Event): Ftrack event causing discover callback. """ if (len(entities) != 1 or entities[0].entity_type.lower() != 'task'): return False entity = entities[0] if entity["parent"].entity_type.lower() == "project": return False ft_project = self.get_project_from_entity(entity) database = pypelib.get_avalon_database() project_name = ft_project["full_name"] avalon_project = database[project_name].find_one({"type": "project"}) if not avalon_project: return False project_apps = avalon_project["config"].get("apps", []) apps = [app["name"] for app in project_apps] if self.identifier in apps: return True return False
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 ''' if ( len(entities) != 1 or entities[0].entity_type.lower() != 'task' ): return False if entities[0]['parent'].entity_type.lower() == 'project': return False ft_project = entities[0]['project'] database = pypelib.get_avalon_database() project_name = ft_project['full_name'] avalon_project = database[project_name].find_one({ "type": "project" }) if avalon_project is None: return False else: apps = [app['name'] for app in avalon_project['config'].get( 'apps', [] )] if self.identifier not in apps: return False return True
def get_avalon_project(ft_project): database = get_avalon_database() project_name = ft_project['full_name'] ca_mongoid = get_ca_mongoid() if ca_mongoid not in ft_project['custom_attributes']: return None # try to find by Id project_id = ft_project['custom_attributes'][ca_mongoid] try: avalon_project = database[project_name].find_one( {'_id': ObjectId(project_id)}) except Exception: avalon_project = None if avalon_project is None: avalon_project = database[project_name].find_one({'type': 'project'}) return avalon_project
def run_application(self, app, project, asset, task, tools, arguments): """Run application in project/asset/task context. With default or specified tools enviornment. This uses pre-defined launcher in `pype-config/launchers` where there must be *toml* file with definition and in platform directory its launcher shell script or binary executables. Arguments will be passed to this script or executable. :param app: Full application name (`maya_2018`) :type app: Str :param project: Project name :type project: Str :param asset: Asset name :type asset: Str :param task: Task name :type task: Str :param tools: Comma separated list of tools (`"mtoa_2.1.0,yeti_4"`) :type tools: Str :param arguments: List of other arguments passed to app :type: List :rtype: None """ import toml import subprocess from pypeapp.lib.Terminal import Terminal from pypeapp import Anatomy t = Terminal() self._initialize() self._update_python_path() import acre from avalon import lib from pype import lib as pypelib abspath = lib.which_app(app) if abspath is None: t.echo("!!! Application [ {} ] is not registered.".format(app)) t.echo("*** Please define its toml file.") return app_toml = toml.load(abspath) executable = app_toml['executable'] app_dir = app_toml['application_dir'] # description = app_toml.get('description', None) # preactions = app_toml.get('preactions', []) launchers_path = os.path.join(os.environ["PYPE_CONFIG"], "launchers") database = pypelib.get_avalon_database() avalon_project = database[project].find_one({"type": "project"}) if avalon_project is None: t.echo( "!!! Project [ {} ] doesn't exists in Avalon.".format(project)) return False # get asset from db avalon_asset = database[project].find_one({ "type": "asset", "name": asset }) avalon_tools = avalon_project["data"]["tools_env"] if tools: avalon_tools = tools.split(",") or [] hierarchy = "" parents = avalon_asset["data"]["parents"] or [] if parents: hierarchy = os.path.join(*parents) data = { "project": { "name": project, "code": avalon_project['data']['code'] }, "task": task, "asset": asset, "app": app_dir, "hierarchy": hierarchy, } anatomy = Anatomy(project) anatomy_filled = anatomy.format(data) workdir = os.path.normpath(anatomy_filled["work"]["folder"]) # set PYPE_ROOT_* environments anatomy.set_root_environments() # set environments for Avalon os.environ["AVALON_PROJECT"] = project os.environ["AVALON_SILO"] = None os.environ["AVALON_ASSET"] = asset os.environ["AVALON_TASK"] = task os.environ["AVALON_APP"] = app.split("_")[0] os.environ["AVALON_APP_NAME"] = app os.environ["AVALON_WORKDIR"] = workdir os.environ["AVALON_HIERARCHY"] = hierarchy try: os.makedirs(workdir) except FileExistsError: pass tools_attr = [os.environ["AVALON_APP"], os.environ["AVALON_APP_NAME"]] tools_attr += avalon_tools print("TOOLS: {}".format(tools_attr)) tools_env = acre.get_tools(tools_attr) env = acre.compute(tools_env) env = acre.merge(env, current_env=dict(os.environ)) env = {k: str(v) for k, v in env.items()} # sanitize slashes in path env["PYTHONPATH"] = env["PYTHONPATH"].replace("/", "\\") env["PYTHONPATH"] = env["PYTHONPATH"].replace("\\\\", "\\") launchers_path = os.path.join(launchers_path, platform.system().lower()) execfile = None if sys.platform == "win32": # test all avaliable executable format, find first and use it for ext in os.environ["PATHEXT"].split(os.pathsep): fpath = os.path.join(launchers_path.strip('"'), executable + ext) if os.path.isfile(fpath) and os.access(fpath, os.X_OK): execfile = fpath break # Run SW if was found executable if execfile is not None: try: t.echo(">>> Running [ {} {} ]".format( executable, " ".join(arguments))) args = [execfile] args.extend(arguments) subprocess.run(args, env=env) except ValueError as e: t.echo("!!! Error while launching application:") t.echo(e) return else: t.echo( "!!! cannot find application launcher [ {} ]".format(app)) return if sys.platform.startswith('linux'): execfile = os.path.join(launchers_path.strip('"'), executable) if os.path.isfile(execfile): try: fp = open(execfile) except PermissionError as p: t.echo("!!! Access denied on launcher [ {} ]".format(app)) t.echo(p) return fp.close() else: t.echo("!!! Launcher doesn\'t exist [ {} ]".format(execfile)) return # Run SW if was found executable if execfile is not None: args = ['/usr/bin/env', 'bash', execfile] args.extend(arguments) t.echo(">>> Running [ {} ]".format(" ".join(args))) try: subprocess.run(args, env=env) except ValueError as e: t.echo("!!! Error while launching application:") t.echo(e) return else: t.echo( "!!! cannot find application launcher [ {} ]".format(app)) return
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 ''' entity = entities[0] project_name = entity['project']['full_name'] database = pypelib.get_avalon_database() # Get current environments env_list = [ 'AVALON_PROJECT', 'AVALON_SILO', 'AVALON_ASSET', 'AVALON_TASK', 'AVALON_APP', 'AVALON_APP_NAME' ] env_origin = {} for env in env_list: env_origin[env] = os.environ.get(env, None) # set environments for Avalon os.environ["AVALON_PROJECT"] = project_name os.environ["AVALON_SILO"] = entity['ancestors'][0]['name'] os.environ["AVALON_ASSET"] = entity['parent']['name'] os.environ["AVALON_TASK"] = entity['name'] os.environ["AVALON_APP"] = self.identifier.split("_")[0] os.environ["AVALON_APP_NAME"] = self.identifier anatomy = Anatomy() hierarchy = "" parents = database[project_name].find_one({ "type": 'asset', "name": entity['parent']['name'] })['data']['parents'] if parents: hierarchy = os.path.join(*parents) application = avalonlib.get_application(os.environ["AVALON_APP_NAME"]) data = { "root": os.environ.get("PYPE_STUDIO_PROJECTS_MOUNT"), "project": { "name": entity['project']['full_name'], "code": entity['project']['name'] }, "task": entity['name'], "asset": entity['parent']['name'], "app": application["application_dir"], "hierarchy": hierarchy, } av_project = database[project_name].find_one({"type": 'project'}) templates = None if av_project: work_template = av_project.get('config', {}).get('template', {}).get('work', None) work_template = None try: work_template = work_template.format(**data) except Exception: try: anatomy = anatomy.format(data) work_template = anatomy["work"]["folder"] except Exception as exc: msg = "{} Error in anatomy.format: {}".format( __name__, str(exc)) self.log.error(msg, exc_info=True) return {'success': False, 'message': msg} workdir = os.path.normpath(work_template) os.environ["AVALON_WORKDIR"] = workdir try: os.makedirs(workdir) except FileExistsError: pass # collect all parents from the task parents = [] for item in entity['link']: parents.append(session.get(item['type'], item['id'])) # collect all the 'environment' attributes from parents tools_attr = [os.environ["AVALON_APP"], os.environ["AVALON_APP_NAME"]] for parent in reversed(parents): # check if the attribute is empty, if not use it if parent['custom_attributes']['tools_env']: tools_attr.extend(parent['custom_attributes']['tools_env']) break tools_env = acre.get_tools(tools_attr) env = acre.compute(tools_env) env = acre.merge(env, current_env=dict(os.environ)) env = acre.append(dict(os.environ), env) # # tools_env = acre.get_tools(tools) # env = acre.compute(dict(tools_env)) # env = acre.merge(env, dict(os.environ)) # os.environ = acre.append(dict(os.environ), env) # os.environ = acre.compute(os.environ) # Get path to execute st_temp_path = os.environ['PYPE_CONFIG'] os_plat = platform.system().lower() # Path to folder with launchers path = os.path.join(st_temp_path, 'launchers', os_plat) # Full path to executable launcher execfile = None if sys.platform == "win32": for ext in os.environ["PATHEXT"].split(os.pathsep): fpath = os.path.join(path.strip('"'), self.executable + ext) if os.path.isfile(fpath) and os.access(fpath, os.X_OK): execfile = fpath break pass # Run SW if was found executable if execfile is not None: avalonlib.launch(executable=execfile, args=[], environment=env) else: return { 'success': False, 'message': "We didn't found launcher for {0}".format(self.label) } pass if sys.platform.startswith('linux'): execfile = os.path.join(path.strip('"'), self.executable) if os.path.isfile(execfile): try: fp = open(execfile) except PermissionError as p: self.log.exception('Access denied on {0} - {1}'.format( execfile, p)) return { 'success': False, 'message': "Access denied on launcher - {}".format(execfile) } fp.close() # check executable permission if not os.access(execfile, os.X_OK): self.log.error( 'No executable permission on {}'.format(execfile)) return { 'success': False, 'message': "No executable permission - {}".format(execfile) } pass else: self.log.error('Launcher doesn\'t exist - {}'.format(execfile)) return { 'success': False, 'message': "Launcher doesn't exist - {}".format(execfile) } pass # Run SW if was found executable if execfile is not None: avalonlib.launch('/usr/bin/env', args=['bash', execfile], environment=env) else: return { 'success': False, 'message': "We didn't found launcher for {0}".format(self.label) } pass # Change status of task to In progress presets = config.get_presets()["ftrack"]["ftrack_config"] if 'status_update' in presets: statuses = presets['status_update'] actual_status = entity['status']['name'].lower() next_status_name = None for key, value in statuses.items(): if actual_status in value or '_any_' in value: if key != '_ignore_': next_status_name = key break if next_status_name is not None: try: query = 'Status where name is "{}"'.format( next_status_name) status = session.query(query).one() entity['status'] = status session.commit() except Exception: msg = ('Status "{}" in presets wasn\'t found on Ftrack' ).format(next_status_name) self.log.warning(msg) # Set origin avalon environments for key, value in env_origin.items(): if value == None: value = "" os.environ[key] = value return {'success': True, 'message': "Launching {0}".format(self.label)}
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 """ entity = entities[0] project_name = entity["project"]["full_name"] database = pypelib.get_avalon_database() asset_name = entity["parent"]["name"] asset_document = database[project_name].find_one({ "type": "asset", "name": asset_name }) hierarchy = "" asset_doc_parents = asset_document["data"].get("parents") if len(asset_doc_parents) > 0: hierarchy = os.path.join(*asset_doc_parents) application = avalon.lib.get_application(self.identifier) data = { "project": { "name": entity["project"]["full_name"], "code": entity["project"]["name"] }, "task": entity["name"], "asset": asset_name, "app": application["application_dir"], "hierarchy": hierarchy } try: anatomy = Anatomy(project_name) anatomy_filled = anatomy.format(data) workdir = os.path.normpath(anatomy_filled["work"]["folder"]) except Exception as exc: msg = "Error in anatomy.format: {}".format(str(exc)) self.log.error(msg, exc_info=True) return {"success": False, "message": msg} try: os.makedirs(workdir) except FileExistsError: pass # set environments for Avalon prep_env = copy.deepcopy(os.environ) prep_env.update({ "AVALON_PROJECT": project_name, "AVALON_ASSET": asset_name, "AVALON_TASK": entity["name"], "AVALON_APP": self.identifier.split("_")[0], "AVALON_APP_NAME": self.identifier, "AVALON_HIERARCHY": hierarchy, "AVALON_WORKDIR": workdir }) prep_env.update(anatomy.roots_obj.root_environments()) # collect all parents from the task parents = [] for item in entity['link']: parents.append(session.get(item['type'], item['id'])) # collect all the 'environment' attributes from parents tools_attr = [prep_env["AVALON_APP"], prep_env["AVALON_APP_NAME"]] tools_env = asset_document["data"].get("tools_env") or [] tools_attr.extend(tools_env) tools_env = acre.get_tools(tools_attr) env = acre.compute(tools_env) env = acre.merge(env, current_env=dict(prep_env)) env = acre.append(dict(prep_env), env) # Get path to execute st_temp_path = os.environ["PYPE_CONFIG"] os_plat = platform.system().lower() # Path to folder with launchers path = os.path.join(st_temp_path, "launchers", os_plat) # Full path to executable launcher execfile = None if application.get("launch_hook"): hook = application.get("launch_hook") self.log.info("launching hook: {}".format(hook)) ret_val = pypelib.execute_hook(application.get("launch_hook"), env=env) if not ret_val: return { 'success': False, 'message': "Hook didn't finish successfully {0}".format(self.label) } if sys.platform == "win32": for ext in os.environ["PATHEXT"].split(os.pathsep): fpath = os.path.join(path.strip('"'), self.executable + ext) if os.path.isfile(fpath) and os.access(fpath, os.X_OK): execfile = fpath break # Run SW if was found executable if execfile is None: return { "success": False, "message": "We didn't find launcher for {0}".format(self.label) } popen = avalon.lib.launch(executable=execfile, args=[], environment=env) elif (sys.platform.startswith("linux") or sys.platform.startswith("darwin")): execfile = os.path.join(path.strip('"'), self.executable) if not os.path.isfile(execfile): msg = "Launcher doesn't exist - {}".format(execfile) self.log.error(msg) return {"success": False, "message": msg} try: fp = open(execfile) except PermissionError as perm_exc: msg = "Access denied on launcher {} - {}".format( execfile, perm_exc) self.log.exception(msg, exc_info=True) return {"success": False, "message": msg} fp.close() # check executable permission if not os.access(execfile, os.X_OK): msg = "No executable permission - {}".format(execfile) self.log.error(msg) return {"success": False, "message": msg} # Run SW if was found executable if execfile is None: return { "success": False, "message": "We didn't found launcher for {0}".format(self.label) } popen = avalon.lib.launch( # noqa: F841 "/usr/bin/env", args=["bash", execfile], environment=env) # Change status of task to In progress presets = config.get_presets()["ftrack"]["ftrack_config"] if "status_update" in presets: statuses = presets["status_update"] actual_status = entity["status"]["name"].lower() already_tested = [] ent_path = "/".join([ent["name"] for ent in entity["link"]]) while True: next_status_name = None for key, value in statuses.items(): if key in already_tested: continue if actual_status in value or "_any_" in value: if key != "_ignore_": next_status_name = key already_tested.append(key) break already_tested.append(key) if next_status_name is None: break try: query = "Status where name is \"{}\"".format( next_status_name) status = session.query(query).one() entity["status"] = status session.commit() self.log.debug("Changing status to \"{}\" <{}>".format( next_status_name, ent_path)) break except Exception: session.rollback() msg = ("Status \"{}\" in presets wasn't found" " on Ftrack entity type \"{}\"").format( next_status_name, entity.entity_type) self.log.warning(msg) return {"success": True, "message": "Launching {0}".format(self.label)}
def get_data(entity, session, custom_attributes): database = get_avalon_database() entity_type = entity.entity_type if entity_type.lower() == 'project': ft_project = entity elif entity_type.lower() != 'project': ft_project = entity['project'] av_project = get_avalon_project(ft_project) project_name = ft_project['full_name'] data = {} data['ftrackId'] = entity['id'] data['entityType'] = entity_type for cust_attr in custom_attributes: key = cust_attr['key'] if cust_attr['entity_type'].lower() in ['asset']: data[key] = entity['custom_attributes'][key] elif (cust_attr['entity_type'].lower() in ['show'] and entity_type.lower() == 'project'): data[key] = entity['custom_attributes'][key] elif (cust_attr['entity_type'].lower() in ['task'] and entity_type.lower() != 'project'): # Put space between capitals (e.g. 'AssetBuild' -> 'Asset Build') entity_type_full = re.sub(r"(\w)([A-Z])", r"\1 \2", entity_type) # Get object id of entity type query = 'ObjectType where name is "{}"'.format(entity_type_full) ent_obj_type_id = session.query(query).one()['id'] if cust_attr['object_type_id'] == ent_obj_type_id: data[key] = entity['custom_attributes'][key] if entity_type in ['Project']: data['code'] = entity['name'] return data # Get info for 'Data' in Avalon DB tasks = [] for child in entity['children']: if child.entity_type in ['Task']: tasks.append(child['name']) # Get list of parents without project parents = [] folderStruct = [] for i in range(1, len(entity['link']) - 1): parEnt = session.get(entity['link'][i]['type'], entity['link'][i]['id']) parName = parEnt['name'] folderStruct.append(parName) parents.append(parEnt) parentId = None for parent in parents: parentId = database[project_name].find_one({ 'type': 'asset', 'name': parName })['_id'] if parent['parent'].entity_type != 'project' and parentId is None: import_to_avalon(session, parent, ft_project, av_project, custom_attributes) parentId = database[project_name].find_one({ 'type': 'asset', 'name': parName })['_id'] hierarchy = "" if len(folderStruct) > 0: hierarchy = os.path.sep.join(folderStruct) data['visualParent'] = parentId data['parents'] = folderStruct data['tasks'] = tasks data['hierarchy'] = hierarchy return data
def import_to_avalon(session, entity, ft_project, av_project, custom_attributes): database = get_avalon_database() project_name = ft_project['full_name'] output = {} errors = [] ca_mongoid = get_ca_mongoid() # Validate if entity has custom attribute avalon_mongo_id if ca_mongoid not in entity['custom_attributes']: msg = ('Custom attribute "{}" for "{}" is not created' ' or don\'t have set permissions for API').format( ca_mongoid, entity['name']) errors.append({'Custom attribute error': msg}) output['errors'] = errors return output # Validate if entity name match REGEX in schema try: avalon_check_name(entity) except ValidationError: msg = '"{}" includes unsupported symbols like "dash" or "space"' errors.append({'Unsupported character': msg}) output['errors'] = errors return output entity_type = entity.entity_type # Project //////////////////////////////////////////////////////////////// if entity_type in ['Project']: type = 'project' config = get_project_config(entity) schema.validate(config) av_project_code = None if av_project is not None and 'code' in av_project['data']: av_project_code = av_project['data']['code'] ft_project_code = ft_project['name'] if av_project is None: project_schema = pypelib.get_avalon_project_template_schema() item = { 'schema': project_schema, 'type': type, 'name': project_name, 'data': dict(), 'config': config, 'parent': None, } schema.validate(item) database[project_name].insert_one(item) av_project = database[project_name].find_one({'type': type}) elif (av_project['name'] != project_name or (av_project_code is not None and av_project_code != ft_project_code)): msg = ('You can\'t change {0} "{1}" to "{2}"' ', avalon wouldn\'t work properly!' '\n{0} was changed back!') if av_project['name'] != project_name: entity['full_name'] = av_project['name'] errors.append({ 'Changed name error': msg.format('Project name', av_project['name'], project_name) }) if (av_project_code is not None and av_project_code != ft_project_code): entity['name'] = av_project_code errors.append({ 'Changed name error': msg.format('Project code', av_project_code, ft_project_code) }) session.commit() output['errors'] = errors return output else: # not override existing templates! templates = av_project['config'].get('template', None) if templates is not None: for key, value in config['template'].items(): if (key in templates and templates[key] is not None and templates[key] != value): config['template'][key] = templates[key] projectId = av_project['_id'] data = get_data(entity, session, custom_attributes) database[project_name].update_many( {'_id': ObjectId(projectId)}, {'$set': { 'name': project_name, 'config': config, 'data': data, }}) entity['custom_attributes'][ca_mongoid] = str(projectId) session.commit() output['project'] = av_project return output # Asset - ///////////////////////////////////////////////////////////// if av_project is None: result = import_to_avalon(session, ft_project, ft_project, av_project, custom_attributes) if 'errors' in result: output['errors'] = result['errors'] return output elif 'project' not in result: msg = 'During project import went something wrong' errors.append({'Unexpected error': msg}) output['errors'] = errors return output av_project = result['project'] output['project'] = result['project'] projectId = av_project['_id'] data = get_data(entity, session, custom_attributes) # 1. hierarchical entity have silo set to None silo = None if len(data['parents']) > 0: silo = data['parents'][0] name = entity['name'] avalon_asset = None # existence of this custom attr is already checked if ca_mongoid not in entity['custom_attributes']: msg = '"{}" don\'t have "{}" custom attribute' errors.append( {'Missing Custom attribute': msg.format(entity_type, ca_mongoid)}) output['errors'] = errors return output mongo_id = entity['custom_attributes'][ca_mongoid] mongo_id = mongo_id.replace(' ', '').replace('\n', '') try: ObjectId(mongo_id) except Exception: mongo_id = '' if mongo_id is not '': avalon_asset = database[project_name].find_one( {'_id': ObjectId(mongo_id)}) if avalon_asset is None: avalon_asset = database[project_name].find_one({ 'type': 'asset', 'name': name }) if avalon_asset is None: asset_schema = pypelib.get_avalon_asset_template_schema() item = { 'schema': asset_schema, 'name': name, 'silo': silo, 'parent': ObjectId(projectId), 'type': 'asset', 'data': data } schema.validate(item) mongo_id = database[project_name].insert_one(item).inserted_id # Raise error if it seems to be different ent. with same name elif (avalon_asset['data']['parents'] != data['parents'] or avalon_asset['silo'] != silo): msg = ('In Avalon DB already exists entity with name "{0}"' ).format(name) errors.append({'Entity name duplication': msg}) output['errors'] = errors return output # Store new ID (in case that asset was removed from DB) else: mongo_id = avalon_asset['_id'] else: if avalon_asset['name'] != entity['name']: if silo is None or changeability_check_childs(entity) is False: msg = ('You can\'t change name {} to {}' ', avalon wouldn\'t work properly!' '\n\nName was changed back!' '\n\nCreate new entity if you want to change name.' ).format(avalon_asset['name'], entity['name']) entity['name'] = avalon_asset['name'] session.commit() errors.append({'Changed name error': msg}) if (avalon_asset['silo'] != silo or avalon_asset['data']['parents'] != data['parents']): old_path = '/'.join(avalon_asset['data']['parents']) new_path = '/'.join(data['parents']) msg = ('You can\'t move with entities.' '\nEntity "{}" was moved from "{}" to "{}"' '\n\nAvalon won\'t work properly, {}!') moved_back = False if 'visualParent' in avalon_asset['data']: if silo is None: asset_parent_id = avalon_asset['parent'] else: asset_parent_id = avalon_asset['data']['visualParent'] asset_parent = database[project_name].find_one( {'_id': ObjectId(asset_parent_id)}) ft_parent_id = asset_parent['data']['ftrackId'] try: entity['parent_id'] = ft_parent_id session.commit() msg = msg.format(avalon_asset['name'], old_path, new_path, 'entity was moved back') moved_back = True except Exception: moved_back = False if moved_back is False: msg = msg.format(avalon_asset['name'], old_path, new_path, 'please move it back') errors.append({'Hierarchy change error': msg}) if len(errors) > 0: output['errors'] = errors return output database[project_name].update_many({'_id': ObjectId(mongo_id)}, { '$set': { 'name': name, 'silo': silo, 'data': data, 'parent': ObjectId(projectId) } }) entity['custom_attributes'][ca_mongoid] = str(mongo_id) session.commit() return output