Esempio n. 1
0
def initialize_callbacks():

    global ARTELLA_CALLBACKS_CACHE
    if ARTELLA_CALLBACKS_CACHE:
        return

    shutdown_type = None
    if hasattr(callback.Callbacks(), 'ShutdownCallback'):
        shutdown_type = getattr(callback.Callbacks(), 'ShutdownCallback')

    for callback_name in dcc_core.callbacks():
        callback_type = getattr(dcc_core.DccCallbacks, callback_name)[1]['type']
        if callback_type == 'simple':
            callback_type = SimpleCallbackWrapper
        elif callback_type == 'filter':
            callback_type = FilterCallbackWrapper
        else:
            callback_type = SimpleCallbackWrapper

        callback_class = getattr(callback.Callbacks(), '{}Callback'.format(callback_name), None)
        if not callback_class:
            logger.warning(
                'Dcc {} does not provides a Callback implementation for {}Callback. Skipping ...'.format(
                    dcc.name(), callback_name))
            continue

        # This should not be necessary. We added just to be sure that no callbacks are registered in scenes
        # because that could break user DCC session.
        new_callback = ARTELLA_CALLBACKS_CACHE.get(callback_name, None)
        if new_callback:
            new_callback.cleanup()

        ARTELLA_CALLBACKS_CACHE[callback_name] = callback_type(callback_class, shutdown_type)

        logger.debug('Creating Callback: {} | {}'.format(callback_name, callback_class))
Esempio n. 2
0
    def handle_message(self, msg):
        """
        Internal function that handles the response received from Artella Drive App

        :param dict msg: Dictionary containing the response from Artella server
        """

        logger.debug('Handling realtime message: {}'.format(msg))
        if not isinstance(msg, dict):
            logger.warning('Malformed realtime message: {}'.format(msg))
            return

        command_name = msg.get('type')
        dcc_name = dcc.name()

        if command_name.startswith(dcc_name):
            dcc_operation = command_name.split('-')[-1]
            file_path = msg['data']['ARTELLA_FILE']
            if dcc_operation == 'import':
                self._handle_import_message(file_path)
            elif dcc_operation == 'reference':
                self._handle_reference_message(file_path)
        elif command_name == 'authorization-ok':
            logger.debug('websocket connection successful.')
        elif command_name == 'open':
            file_path = msg['data']['ARTELLA_FILE']
            self._handle_open_message(file_path)
        else:
            pass
Esempio n. 3
0
    def get_version_variable_name(self):
        """
        Returns the environment variable name used to store current Arqtella DCC plugin version

        :return: Environment version name
        :rtype: str
        """

        return 'ARTELLA_{}_PLUGIN'.format(dcc.name())
Esempio n. 4
0
    def pass_message(self, json_data):
        """
        Executes handle message Artella plugin functionality in specific DCC thread to make sure that it does not
        conflicts with main DCC UI thread
        :param json_data:

        """

        logger.debug('Passing message to {}: {}'.format(dcc.name(), json_data))
        self.execute_in_main_thread(self.handle_message, json_data)
Esempio n. 5
0
 def closeEvent(self, event):
     if self._plugin_updated:
         dcc_name = dcc.name()
         import artella.loader
         artella.loader._reload()
         if dcc_name == 'maya':
             import maya.cmds as cmds
             cmds.evalDeferred(artella.loader.init)
         else:
             artella.loader.init()
     super(UpdaterWindow, self).closeEvent(event)
Esempio n. 6
0
    def init_ui(self):
        """
        Function that initializes plugin UI related functionality
        """

        main_menu = dccplugin.DccPlugin().get_main_menu()
        if not main_menu:
            return

        plugin_menu = self._config_dict.get('menu')
        plugin_package = self._config_dict.get('package', 'Artella')
        plugin_dccs = self._config_dict.get('dcc', list())

        can_load_plugin = True
        if plugin_dccs:
            can_load_plugin = dcc.name() in plugin_dccs

        if can_load_plugin:
            if plugin_menu and 'label' in plugin_menu:
                menu_parents = plugin_menu.get('parents', None)
                if menu_parents:
                    current_parent = 'Artella' if dcc.check_menu_exists(
                        'Artella') else None
                    for menu_parent in menu_parents:
                        if not dcc.check_menu_exists(menu_parent):

                            # TODO: Before More Artella menu addition we force the creation of a
                            # TODO: separator. We should find a way to avoid hardcoded this.
                            icon = ''
                            if menu_parent == 'More Artella':
                                dcc.add_menu_separator('Artella')
                                icon = 'artella.png'

                            dcc.add_sub_menu_item(menu_parent,
                                                  parent_menu=current_parent,
                                                  icon=icon)
                        current_parent = menu_parent

                menu_label = plugin_menu['label']
                menu_command = plugin_menu['command']
                menu_icon = self._config_dict.get('icon', '')
                if menu_parents:
                    menu_parent = menu_parents[-1]
                    dcc.add_menu_item(menu_label,
                                      menu_command,
                                      menu_parent,
                                      icon=menu_icon)
                else:
                    dcc.add_menu_item(menu_label,
                                      menu_command,
                                      main_menu,
                                      icon=menu_icon)
Esempio n. 7
0
    def _init(self):
        """
        Internal function that initializes info for the plugin and its environment
        """

        from artella import dcc

        self._info.update({
            'name': self._plugin.__class__.__name__,
            'module': self._plugin.__class__.__module__,
            'filepath': inspect.getfile(self._plugin.__class__),
            'id': self._id,
            'application': dcc.name()
        })
Esempio n. 8
0
def get_latest_stable_artella_dcc_plugin_info(dcc_name=None, platform=None, show_dialogs=False):
    """
    Returns plugin info data from Artella server

    :param str dcc_name: name of the DCC plugin we want to retrieve to retrieve info from. If not give current DCC
        will be used
    :param str platform: name of the OS platform we want to retrieve DCC plugin of (windows, darwin and linux)
    :return: Dictionary containing plugin info data
    :rtype: dict
    """

    dcc_plugin_info = dict()
    message_title = 'Impossible to retrieve version info'

    dcc_name = dcc_name or dcc.name()

    current_platform = platform or None
    if not current_platform:
        if utils.is_windows():
            current_platform = 'windows'
        elif utils.is_mac():
            current_platform = 'darwin'
        elif utils.is_linux():
            current_platform = 'linux'
    if not current_platform:
        msg = 'Impossible to retrieve Dcc plugin info from Artella server because ' \
              'current OS platform is not supported: "{}"'.format(sys.platform)
        logger.warning(msg)
        if show_dialogs:
            qtutils.show_warning_message_box(message_title, msg)
        return dcc_plugin_info

    artella_url = 'https://updates.artellaapp.com/plugins/{}/versions/stable-{}.json'.format(dcc_name, current_platform)
    req = Request(artella_url)

    rsp = None
    try:
        rsp = urlopen(req)
    except Exception:
        try:
            ssl._create_default_https_context = ssl._create_unverified_context
            # To avoid [SSL: CERTIFICATE_VERIFY_FAILED] errors.
            context = ssl._create_unverified_context()
            rsp = urlopen(req, context=context)
        except URLError as exc:
            if hasattr(exc, 'reason'):
                msg = 'Failed to retrieve Artella DCC plugin info ¨{}({})" from  Artella server ({}): "{}"'.format(
                    dcc_name, current_platform, artella_url, exc.reason)
            elif hasattr(exc, 'code'):
                msg = 'Failed to retrieve Artella DCC plugin info ¨{}({})" from  Artella server ({}): "{}"'.format(
                    dcc_name, current_platform, artella_url, exc.code)
            else:
                msg = exc
            logger.debug(exc)
            logger.error(msg)
            if show_dialogs:
                qtutils.show_error_message_box(message_title, msg)

    warning_message = 'Was not possible to retrieve DCC Artella plugin info ¨{}({})" from  Artella server'.format(
        dcc_name, current_platform)

    if not rsp:
        if show_dialogs:
            qtutils.show_warning_message_box(message_title, warning_message)
        return dcc_plugin_info

    artella_rsp = rsp.read()
    if not artella_rsp:
        if show_dialogs:
            qtutils.show_warning_message_box(message_title, warning_message)
        return dcc_plugin_info

    try:
        dcc_plugin_data = json.loads(artella_rsp)
    except Exception as exc:
        msg = 'Error while reading data from Artella DCC plugin info ¨{}({})": {}'.format(
            dcc_name, current_platform, exc)
        logger.error(msg)
        if show_dialogs:
            qtutils.show_error_message_box(message_title, msg)
        return dcc_plugin_info

    if not dcc_plugin_data:
        if show_dialogs:
            qtutils.show_warning_message_box(message_title, warning_message)
        return dcc_plugin_info

    dcc_plugin_info['platform'] = dcc_plugin_data.get('platform', '')
    dcc_plugin_info['version'] = dcc_plugin_data.get('version', '0.0.0')
    dcc_plugin_info['file_name'] = dcc_plugin_data.get('file_name', '')
    dcc_plugin_info['url'] = dcc_plugin_data.get('url', '')

    return dcc_plugin_info
Esempio n. 9
0
    def local_path_to_uri(self, file_path):
        """
        Translates a local file path to its URI format
        :param str file_path: Absolute local file path we want to translate to URI
        :param str prefix:

        :return: path in its URI format if current DCC supports this feature; path without any change otherwise
        :rtype: str
        """

        if not dcc.supports_uri_scheme():
            logger.warning('Current DCC {} does not supports Artella URI scheme!'.format(dcc.name()))
            return file_path

        return file_path
Esempio n. 10
0
def load_registered_plugins(dev=False):
    """
    Loads all the plugins found in the registered plugin paths
    :return:
    """

    plugin_paths = list(
        set([
            utils.clean_path(plugin_path) for plugin_path in _PLUGIN_PATHS
            if os.path.isdir(plugin_path)
        ]))
    if not plugin_paths:
        logger.info('No Artella Plugins found to load!')
        return

    found_paths = dict()

    for plugin_path in plugin_paths:
        for root, dirs, files in os.walk(plugin_path):
            if consts.ARTELLA_PLUGIN_CONFIG not in files:
                continue
            clean_path = utils.clean_path(root)
            found_paths[clean_path] = os.path.join(
                root, consts.ARTELLA_PLUGIN_CONFIG)

    if not found_paths:
        logger.info('No plugins found in registered plugin paths: {}'.format(
            _PLUGIN_PATHS))
        return

    for plugin_path in plugin_paths:
        for plugin_dir in os.listdir(plugin_path):
            clean_path = utils.clean_path(os.path.join(plugin_path,
                                                       plugin_dir))
            if os.path.isdir(clean_path) and clean_path not in sys.path:
                sys.path.append(clean_path)

    for plugin_path, plugin_config in found_paths.items():

        # Search sub modules paths
        sub_modules_found = list()
        for sub_module in utils.iterate_modules(plugin_path):
            file_name = os.path.splitext(os.path.basename(sub_module))[0]
            if file_name.startswith('_') or file_name.startswith(
                    'test_') or sub_module.endswith('.pyc'):
                continue
            if not sub_module or sub_module in sub_modules_found:
                continue
            sub_modules_found.append(sub_module)
        if not sub_modules_found:
            continue

        # Find specific DCC plugin implementation
        dcc_name = dcc.name()
        sub_module = sub_modules_found[0]

        max_length = 50
        index = 0
        artella_module_parts = list()
        temp_sub_module = sub_module
        while True:
            if index > max_length:
                artella_module_parts = list()
                break
            base_name = os.path.basename(temp_sub_module)
            if not base_name or base_name == 'artella':
                artella_module_parts.append(base_name)
                break
            artella_module_parts.append(base_name)
            temp_sub_module = os.path.dirname(temp_sub_module)
            index += 1
        if not artella_module_parts:
            module_path = utils.convert_module_path_to_dotted_path(
                os.path.normpath(sub_module))
        else:
            module_path = os.path.splitext('.'.join(
                reversed(artella_module_parts)))[0]

        module_path_split = module_path.split('.')
        dcc_module_path = '{}.{}.{}'.format('.'.join(module_path_split[:-1]),
                                            dcc_name, module_path_split[-1])
        sub_module_obj = utils.import_module(dcc_module_path,
                                             skip_exceptions=True)
        if not sub_module_obj:
            sub_module_obj = utils.import_module(module_path)
            if not sub_module_obj:
                logger.error(
                    'Error while importing Artella Plugin module: {}'.format(
                        module_path))
                continue

        if dev:
            plugin_version = 'DEV'
        else:
            plugin_version = None
            module_path_dir = module_path.rsplit('.', 1)[0]
            version_module_path = '{}.__version__'.format(module_path_dir)
            version_module_path = version_module_path.replace(
                '.{}.__'.format(dcc_name), '.__')
            try:
                version_module_obj = utils.import_module(version_module_path)
            except Exception:
                version_module_obj = None
            if version_module_obj:
                try:
                    plugin_version = version_module_obj.get_version()
                except Exception as exc:
                    logger.warning(
                        'Impossible to retrieve version for Artella Plugin module: {} | {}'
                        .format(module_path, exc))

        for member in utils.iterate_module_members(sub_module_obj,
                                                   predicate=inspect.isclass):
            register_plugin(member[1], plugin_config,
                            os.path.dirname(sub_module), plugin_version)

    if not _PLUGINS:
        logger.warning('No Artella plugins found to load!')
        return

    ordered_plugins_list = list()
    for plugin_id, plugin_dict in _PLUGINS.items():
        plugin_class = plugin_dict['class']
        plugin_index = plugin_class.INDEX or -1
        index = 0
        for i, plugin_item in enumerate(ordered_plugins_list):
            plugin_item_index = list(plugin_item.values())[0]['index']
            if plugin_index < plugin_item_index:
                index += 1
        ordered_plugins_list.insert(
            index, {plugin_id: {
                'index': plugin_index,
                'dict': plugin_dict
            }})

    for plugin_item in ordered_plugins_list:
        plugin_id = list(plugin_item.keys())[0]
        plugin_dict = list(plugin_item.values())[0]['dict']
        plugin_class = plugin_dict['class']
        plugin_config_dict = plugin_dict.get('config', dict())
        try:
            plugin_inst = plugin_class(plugin_config_dict)
        except Exception:
            logger.error(
                'Impossible to instantiate Artella Plugin: "{}"'.format(
                    plugin_id))
            logger.error(traceback.format_exc())
            continue
        _PLUGINS[plugin_id]['plugin_instance'] = plugin_inst
Esempio n. 11
0
def register_plugin(class_obj,
                    config_path,
                    plugin_path,
                    plugin_version=None,
                    plugin_interface=None):
    """
    Registers an Artella plugin instance into the manager
    :param class_obj:
    :param config_path:
    :param plugin_path:
    :param plugin_version:
    :param plugin_interface:
    :return:
    :rtype: bool
    """

    if not config_path or not os.path.isfile(config_path):
        logger.warning(
            'Impossible to register Artella Plugin: {} because its config file does not exists: {}!'
            .format(class_obj, config_path))
        return False

    plugin_interface = plugin_interface or plugin.ArtellaPlugin

    if not issubclass(class_obj, plugin_interface):
        return True

    plugin_id = None
    if hasattr(class_obj, 'ID'):
        plugin_id = getattr(class_obj, 'ID')
    if not plugin_id:
        plugin_id = class_obj.__name__
    if plugin_id in _PLUGINS:
        logger.warning(
            'Artella Plugin: "{}" already registered!'.format(plugin_id))
        return True

    try:
        plugin_config = utils.read_json(config_path)
    except Exception:
        logger.warning(
            'Artella Plugin "{}" configuration file "{}" has not a proper structure. Skipping plugin ...'
            .format(plugin_id, config_path))
        return False

    plugin_name = plugin_config.get('name', None)
    plugin_package = plugin_config.get('package', None)
    plugin_icon = plugin_config.get('icon', None)
    plugin_resources = plugin_config.get('resources', None)

    # Register DCC resources, both DCC specific plugin implementation
    # resources and generic implementation resources
    plugin_resources_paths = list()
    if plugin_resources and plugin_path:
        plugin_path_base = os.path.basename(plugin_path)
        if plugin_path_base == dcc.name():
            plugin_resources_paths.append(
                os.path.join(plugin_path, plugin_resources))
            plugin_resources_paths.append(
                os.path.join(os.path.dirname(plugin_path), plugin_resources))
        else:
            plugin_resources_paths.append(
                os.path.join(plugin_path, plugin_resources))
        for plugin_resources_path in plugin_resources_paths:
            if os.path.isdir(plugin_resources_path):
                dcc.register_dcc_resource_path(plugin_resources_path)
                icons_path = os.path.join(plugin_resources_path, 'icons')
                if os.path.isdir(icons_path):
                    dcc.register_dcc_resource_path(icons_path)

    _PLUGINS[plugin_id] = {
        'name': plugin_name,
        'package': plugin_package,
        'icon': plugin_icon,
        'class': class_obj,
        'config': plugin_config,
        'version': plugin_version,
        'resource_paths': plugin_resources_paths
    }

    logger.info(
        'Artella Plugin: "{}" registered successfully!'.format(plugin_id))

    return True
Esempio n. 12
0
    def uninstall(self, show_dialogs=True):
        artella_path = artella.__path__[0]
        if not os.path.isdir(artella_path):
            msg = 'Artella folder "{}" does not exists!'.format(artella_path)
            if show_dialogs:
                api.show_warning_message(text=msg)
            else:
                logger.warning(msg)
            return False

        res = qtutils.show_question_message_box(
            'Artella Uninstaller',
            'All plugins will be removed.\n\nArtella plugin will not be accessible by any DCC after uninstall.\n\n'
            'Are you sure you want to uninstall Artella Plugin?')
        if not res:
            return False

        do_remove_install_folder = not dccplugin.DccPlugin().dev

        valid_uninstall = self._uninstall(artella_path)
        if not valid_uninstall:
            msg = 'Artella uninstall process was not completed!'.format(artella_path)
            if show_dialogs:
                api.show_error_message(text=msg)
            else:
                logger.error(msg)
            return False

        loader.shutdown(dev=False)

        if do_remove_install_folder:
            try:
                logger.info('Removing Artella Dcc Plugin directory: {}'.format(artella_path))
                utils.delete_folder(artella_path)
            except Exception as exc:
                logger.warning(
                    'Impossible to remove Artella Dcc plugin directory: {} | {}'.format(artella_path, exc))
                return False

        if os.path.isdir(artella_path):
            msg = 'Artella folder was not removed during uninstall process.\n\n{}\n\n Remove it manually if you ' \
                  'want to have a complete clean uninstall of Artella plugin.'.format(artella_path)
            if show_dialogs:
                dcc.show_info('Artella Uninstaller', msg)
            else:
                logger.info(msg)
            utils.open_folder(os.path.dirname(artella_path))

        # Remove specific DCC install folder if exists
        root_dcc_install_dir = os.path.dirname(os.path.dirname(os.path.dirname(artella_path)))
        dcc_install_dir = os.path.join(root_dcc_install_dir, dcc.name())
        if os.path.isdir(dcc_install_dir):
            utils.delete_folder(dcc_install_dir)

        # Cleanup artella directories from
        artella_dir = os.path.dirname(artella_path)
        sys_paths = [artella_path, artella_dir, utils.clean_path(artella_path), utils.clean_path(artella_dir)]
        paths_to_remove = list()
        for sys_path in sys.path:
            if sys_path in sys_paths:
                paths_to_remove.append(sys_path)
                sys.path.remove(sys_path)
            elif 'artella-plugins' in sys_path or 'artella-dccs' in sys_path:
                paths_to_remove.append(sys_path)
        for path_to_remove in paths_to_remove:
            if path_to_remove not in sys.path:
                continue
            sys.path.remove(path_to_remove)

        return True
Esempio n. 13
0
        def _fill_data(self):

            install_path = r'D:\dev\artella\test_download'

            dcc_plugins = list()
            dcc_install_package = 'artella-installer-{}'.format(dcc.name())
            base_file_name = 'artella_installer_maya'
            file_name = '{}.tar.gz'.format(base_file_name)
            file_path = os.path.join(install_path, file_name)
            dcc_pypi_info = utils.get_pypi_info(dcc_install_package)
            if dcc_pypi_info:
                dcc_url = dcc_pypi_info.get('url', '')
                if dcc_url:
                    valid = utils.download_and_extract_package_from_pypi(dcc_url, file_path, install_path)
                    if valid:
                        config_file = None
                        print('Install Path: {}'.format(install_path))
                        for root, dirs, files in os.walk(install_path):
                            if config_file:
                                break
                            for file_path in files:
                                if file_path == 'artella-installer.json':
                                    config_file = os.path.join(root, file_path)
                                    break
                        if config_file and os.path.isfile(config_file):
                            config_data = core_utils.read_json(config_file)
                            config_plugins = config_data.get('plugins', list())
                            for config_plugin in config_plugins:
                                plugin_id = config_plugin.get('id', '')
                                if not plugin_id:
                                    plugin_repo = config_plugin.get('repo', '')
                                    if plugin_repo:
                                        plugin_id = plugin_repo.split('/')[-1]
                                if plugin_id:
                                    dcc_plugins.append(plugin_id)
            dcc_plugins = list(set(dcc_plugins))

            all_plugins = plugins.plugins()
            for plugin_id, plugin_data in all_plugins.items():

                plugin_name = plugin_data['name']
                plugin_icon_name = plugin_data.get('icon', None)
                plugin_package = plugin_data.get('package', None)
                plugin_version = plugin_data.get('version', None)
                plugin_resource_paths = plugin_data.get('resource_paths', list())

                if plugin_id in dcc_plugins:
                    dcc_plugins.remove(plugin_id)

                plugin_icon_pixmap = None
                if plugin_icon_name and plugin_resource_paths:
                    for plugin_resource_path in plugin_resource_paths:
                        plugin_icon_path = os.path.join(plugin_resource_path, plugin_icon_name)
                        if os.path.isfile(plugin_icon_path):
                            plugin_icon_pixmap = QtGui.QPixmap(plugin_icon_path)

                if plugin_package not in self._plugins:
                    package_layout = self._add_package_tab(plugin_package)
                    self._plugins[plugin_package] = {'layout': package_layout, 'plugins': []}
                else:
                    package_layout = self._plugins[plugin_package]['layout']

                pypi_info = utils.get_pypi_info(plugin_id)
                if not pypi_info:
                    continue

                plugin_author = pypi_info.get('author', '')
                plugin_author_email = pypi_info.get('author_email', '')
                plugin_summary = pypi_info.get('summary', '')
                plugin_latest_version = pypi_info.get('version', '')    # this is the latest version of the plugin in PyPI
                plugin_upload_date = pypi_info.get('upload_date', '')
                plugin_size = pypi_info.get('size', '')
                plugin_url = pypi_info.get('url', '')

                new_plugin_widget = plugin.PluginVersionWidget(
                    plugin_id, plugin_name, plugin_package, plugin_version, plugin_author, plugin_author_email,
                    plugin_summary, plugin_latest_version, plugin_upload_date, plugin_size, plugin_url, plugin_icon_pixmap)
                new_plugin_widget.updated.connect(self._on_updated_plugin)
                self._plugins[plugin_package]['plugins'].append(new_plugin_widget)
                package_layout.addWidget(new_plugin_widget)

            print(dcc_plugins)
Esempio n. 14
0
    def _fill_data(self):
        current_plugin_version = dccplugin.DccPlugin().get_version() or 'Undefined'
        self._artella_dcc_plugin_version_label.setText(current_plugin_version)

        added_packages = dict()

        # Retrieve Artella plugins versions
        all_plugins = plugins.plugins()
        for plugin_id, plugin_data in all_plugins.items():
            plugin_package = plugin_data.get('package', 'Not Defined')
            package_item = added_packages.get(plugin_package, None)
            if not package_item:
                package_item = QtWidgets.QTreeWidgetItem([plugin_package])
                self._plugins_tree.addTopLevelItem(package_item)
                added_packages[plugin_package] = package_item
            plugin_name = plugin_data['name']
            plugin_version = plugin_data.get('version', 'Undefined')
            plugin_item = QtWidgets.QTreeWidgetItem([plugin_name, plugin_version, plugin_id])
            package_item.addChild(plugin_item)

        package_item = added_packages.get('Artella', None)
        if not package_item:
            package_item = QtWidgets.QTreeWidgetItem(['Artella'])
            self._plugins_tree.addTopLevelItem(package_item)

        # Retrieve DCC plugin version
        dcc_version = None
        dcc_module_name = core_dcc.CURRENT_DCC_MODULE
        if dcc_module_name:
            try:
                dcc_module_version = '{}.__version__'.format(dcc_module_name)
                dcc_version_mod = utils.import_module(dcc_module_version)
                dcc_version = dcc_version_mod.get_version()
            except Exception as exc:
                logger.warning('Impossible to retrieve {} Artella plugin version: {}'.format(dcc.name(), exc))
        dcc_version = dcc_version or 'Undefined'
        dcc_version_item = QtWidgets.QTreeWidgetItem([dcc.nice_name(), dcc_version, dcc_module_name.replace('.', '- ')])
        package_item.insertChild(0, dcc_version_item)

        # Retrieve Artella core version
        core_version = None
        core_version_path = 'artella.__version__'
        try:
            core_version_mod = utils.import_module(core_version_path)
            core_version = core_version_mod.get_version()
        except Exception as exc:
            logger.warning('Impossible to retrieve Artella Core version: {}'.format(exc))
        core_version = core_version or 'Undefined'
        core_version_item = QtWidgets.QTreeWidgetItem(['Core', core_version, 'artella-plugins-core'])
        package_item.insertChild(0, core_version_item)

        self._plugins_tree.expandAll()
        self._plugins_tree.resizeColumnToContents(0)
        self._plugins_tree.resizeColumnToContents(1)
        self._plugins_tree.resizeColumnToContents(2)