コード例 #1
0
    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
コード例 #2
0
ファイル: ftrack_app_handler.py プロジェクト: jonike/pype
    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
コード例 #3
0
ファイル: avalon_sync.py プロジェクト: 81819152/pype
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
コード例 #4
0
    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
コード例 #5
0
    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)}
コード例 #6
0
    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)}
コード例 #7
0
ファイル: avalon_sync.py プロジェクト: 81819152/pype
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
コード例 #8
0
ファイル: avalon_sync.py プロジェクト: 81819152/pype
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