def installed_plugins(only_conda=False): ''' .. versionadded:: 0.20 Parameters ---------- only_conda : bool, optional Only consider plugins that are installed **as Conda packages**. .. versionadded:: 0.22 Returns ------- list List of properties corresponding to each available plugin that is **installed**. .. versionchanged:: 0.22 If :data:`only_conda` is ``False``, a plugin is assumed to be *installed* if it is present in the ``share/microdrop/plugins/available`` directory **and** is a **real** directory (i.e., not a link). If :data:`only_conda` is ``True``, only properties for plugins that are installed **as Conda packages** are returned. ''' available_path = MICRODROP_CONDA_SHARE.joinpath('plugins', 'available') if not available_path.isdir(): return [] installed_plugins_ = [] for plugin_path_i in available_path.dirs(): # Only process plugin directory if it is *not a link*. if not _islinklike(plugin_path_i): # Read plugin package info from `properties.yml` file. try: with plugin_path_i.joinpath('properties.yml').open('r') as input_: properties_i = yaml.load(input_.read()) except: logger.info('[warning] Could not read package info: `%s`', plugin_path_i.joinpath('properties.yml'), exc_info=True) else: properties_i['path'] = plugin_path_i.realpath() installed_plugins_.append(properties_i) if only_conda: # Only consider plugins that are installed **as Conda packages**. try: package_names = [plugin_i['package_name'] for plugin_i in installed_plugins_] conda_package_infos = ch.package_version(package_names, verbose=False) except ch.PackageNotFound, exception: # At least one specified plugin package name did not correspond to an # installed Conda package. logger.warning(str(exception)) conda_package_infos = exception.available # Extract name from each Conda plugin package. installed_package_names = set([package_i['name'] for package_i in conda_package_infos]) return [plugin_i for plugin_i in installed_plugins_ if plugin_i['package_name'] in installed_package_names]
def enabled_plugins(installed_only=True): ''' .. versionadded:: 0.21 Parameters ---------- installed_only : bool, optional Only consider enabled plugins that are installed in the Conda environment. Returns ------- list List of properties corresponding to each plugin that is **enabled**. If :data:`installed_only` is True``, only consider plugins: 1. Present in the ``etc/microdrop/plugins/enabled`` directory as a link/junction to a **real** directory (i.e., not a link) in the ``share/microdrop/plugins/available`` directory. 2. Matching the name of a package in the Conda environment. If :data:`installed_only` is ``False``, consider all plugins present in the ``etc/microdrop/plugins/enabled`` directory as either a *real* directory or a link/junction. ''' enabled_path = MICRODROP_CONDA_PLUGINS.joinpath('enabled') if not enabled_path.isdir(): return [] # Construct list of property dictionaries, one per enabled plugin # directory. enabled_plugins_ = [] for plugin_path_i in enabled_path.dirs(): if not installed_only or _islinklike(plugin_path_i): # Enabled plugin path is either **a link to an installed plugin** # or call explicitly specifies that plugins that are not installed # should still be considered. # Read plugin package info from `properties.yml` file. try: with plugin_path_i.joinpath('properties.yml').open('r') as input_: properties_i = yaml.load(input_.read()) except: logger.info('[warning] Could not read package info: `%s`', plugin_path_i.joinpath('properties.yml'), exc_info=True) continue else: properties_i['path'] = plugin_path_i.realpath() enabled_plugins_.append(properties_i) if installed_only: # Only consider enabled plugins that are installed in the Conda # environment. try: # Attempt to look up installed Conda package info for each enabled # plugin. package_names = [properties_i['package_name'] for properties_i in enabled_plugins_] installed_info = ch.package_version(package_names, verbose=False) except ch.PackageNotFound, exception: # Failed to find a corresponding installed Conda package for at # least one enabled plugin. logger.warning(str(exception)) available_names = set([package_i['name'] for package_i in exception.available]) # Only return list of enabled plugins that have a corresponding # Conda package installed. return [properties_i for properties_i in enabled_plugins_ if properties_i['package_name'] in available_names]
def conda_version_info(package_name): ''' .. versionadded:: 0.2.post5 .. versionchanged:: 0.3.post2 Add support for running in Conda environments. .. versionchanged:: 0.7.3 Use :func:`conda_helpers.conda_exec` to search for available MicroDrop Conda packages. Add ``sci-bots`` Anaconda channel to Conda package search. .. versionchanged:: 0.7.8 Fall back to using ``conda list`` to search for the installed version of MicroDrop if the version cannot be determined using ``conda search``. Parameters ---------- package_name : str Conda package name. Returns ------- dict Version information: - ``latest``: Latest available version. - ``installed``: Conda package description dictionary for installed version (`None` if not installed). Raises ------ IOError If Conda executable not found. subprocess.CalledProcessError If `conda search` command fails. This happens, for example, if no internet connection is available. ''' # Use `-f` flag to search for package, but *no other packages that have # `<package_name>` in the name). json_output = ch.conda_exec('search', '-c', 'sci-bots', '-c', 'wheeler-microfluidics', '-f', 'microdrop', '--json', verbose=False) versions = json.loads(json_output)['microdrop'] installed_versions = [v_i for v_i in versions if v_i['installed']] installed_version = installed_versions[0] if installed_versions else None if installed_version is None: # If not able to find installed version from `microdrop` Conda package # search, use `conda list ...` to try determine the installed version of # MicroDrop. try: installed_version = ch.package_version('microdrop', verbose=False) except NameError: # Installed MicroDrop Conda package not found (perhaps this is a # development environment?) pass return {'installed': installed_version, 'versions': versions}
------ IOError If Conda update server cannot be reached, e.g., if there is no network connection available. See also -------- _update_plugin_ui ''' try: update_json_log = plugin_install(package_name) except RuntimeError, exception: if 'CondaHTTPError' in str(exception): raise IOError('Error accessing update server.') else: raise if update_json_log.get('success'): if 'actions' in update_json_log: # Plugin was updated successfully. # Display prompt indicating previous version # and new version. actions = update_json_log['actions'] update_json_log['old_versions'] = actions.get('UNLINK', []) update_json_log['new_versions'] = actions.get('LINK', []) else: # No update available. version_dict = ch.package_version(package_name) update_json_log.update(version_dict) return update_json_log
def update_plugin_dialog(package_name=None, update_args=None, update_kwargs=None, ignore_not_installed=True): ''' Launch dialog to track status of update of specified plugin package. .. versionadded:: 0.19 .. versionchanged:: 0.20 Add support for updating multiple packages (update all **installed** plugins by default). Add :data:`update_args` and :data:`update_kwargs` arguments. .. versionchanged:: 0.20.1 Disable "OK" button until update has completed. Add "Cancel" button. .. versionchanged:: 0.20.3 Fix typo in handling of attempt to update plugin packages that are not installed. .. versionchanged:: 0.23 Add :data:`ignore_not_installed` parameter. .. versionchanged:: 0.23.1 Fix bug in status message markup syntax when no packages are unlinked **or** linked. Parameters ---------- package_name : str or list, optional Conda MicroDrop plugin package name, e.g., `microdrop.mr-box-plugin`. Multiple plugin package names may be provided as a list. If no package name(s) specified, update **all installed** plugin packages. update_args : list or tuple, optional Extra arguments to pass to :func:`mpm.api.update` call. update_kwargs : dict, optional Extra keyword arguments to pass to :func:`mpm.api.update` call. ignore_not_installed : bool, optional If ``True`` (*default*), ignore plugin packages that are not installed as Conda packages. Otherwise, a :class:`conda_heplers.PackageNotFound` exception will be raised for plugins that are not installed as Conda packages. Returns ------- dict or None Conda install log. If dialog is closed or cancelled, ``None`` is returned. Notes ----- This function launches two threads; one to pulse the progress bar periodically, and one to run the actual update attempt. ''' thread_context = {} if package_name is None: # No plugin package specified. Update all plugins which are installed # as Conda packages. installed_plugins_ = installed_plugins(only_conda=True) installed_package_names = [plugin_i['package_name'] for plugin_i in installed_plugins_] package_name = installed_package_names logger.info('Update all plugins installed as Conda packages.') else: # At least one plugin package name was explicitly specified. if isinstance(package_name, types.StringTypes): package_name = [package_name] # Only update plugins that are installed as Conda packages. try: conda_package_infos = ch.package_version(package_name, verbose=False) except ch.PackageNotFound, exception: # At least one specified plugin package name did not correspond to an # installed Conda package. if not ignore_not_installed: # Raise error indicating at least one plugin is not installed # as a Conda package. raise logger.warning(str(exception)) conda_package_infos = exception.available # Extract name from each Conda plugin package. package_name = [package_i['name'] for package_i in conda_package_infos] logger.info('Update the following plugins: %s', ', '.join('`{}`'.format(name_i) for name_i in package_name))
'no network connection.') return install_log_json = _strip_conda_menuinst_messages(install_log_json) install_log_json = _strip_progress_messages(install_log_json) try: install_response = json.loads(install_log_json) except ValueError: # Error decoding JSON response. # XXX Assume install succeeded. print >> sys.stderr, ('Warning: could not decode ' '`microdrop-launcher` install log:') print >> sys.stderr, install_log_json return unlinked, linked = ch.install_info(install_response) print 'Uninstall:' print '\n'.join(' - `{} (from {})`'.format(package_i, channel_i) for package_i, channel_i in unlinked) print '' print 'Install:' print '\n'.join(' - `{} (from {})`'.format(package_i, channel_i) for package_i, channel_i in linked) else: # No new version of the launcher is available for installation. print('Up to date: {}'.format( ch.package_version('microdrop-launcher', verbose=False).get('dist_name'))) if __name__ == '__main__': main()
def check_version_cache_for_upgrade(): ''' Prompt user to offer to upgrade if cached latest MicroDrop version is newer than currently installed version. .. versionadded:: 0.7.8 ''' # Get currently installed `microdrop` package information. # # Example `installed_info`: # # {u'base_url': None, # u'build_number': 0, # u'build_string': u'0', # u'channel': u'sci-bots', # u'dist_name': u'microdrop-2.10.2-0', # u'name': u'microdrop', # u'platform': None, # u'version': u'2.10.2', # u'with_features_depends': None} try: installed_info = ch.package_version('microdrop', verbose=False) except NameError: # Installed MicroDrop Conda package not found (perhaps this is a # development environment?) return cached_path, cached_info = load_cached_version() latest_version = cached_info.get('version') installed_version = installed_info.get('version') # If cached latest MicroDrop version is more recent than the currently # installed version, prompt user to offer to upgrade. if all([GUI_AVAILABLE, not cached_info.get('ignore'), latest_version is not None]): if (pkg_resources.parse_version(latest_version) <= pkg_resources.parse_version(installed_version)): return # Display dialog. dialog = gtk.MessageDialog(type=gtk.MESSAGE_QUESTION) dialog.set_icon_from_file(ICON_PATH) dialog.set_title('Upgrade to MicroDrop v{}'.format(latest_version)) dialog.add_buttons(gtk.STOCK_YES, gtk.RESPONSE_YES, "Not now", gtk.RESPONSE_NO, "Never", gtk.RESPONSE_CANCEL) dialog.set_markup('A new version of MicroDrop is available.\n\n' 'Would you like to upgrade to MicroDrop v{} (current' ' version: v{})?'.format(latest_version, installed_version)) response = dialog.run() dialog.destroy() if response == gtk.RESPONSE_CANCEL: # Ignore this specific version from now on. try: with cached_path.open('w') as output: cached_info = {'version': latest_version, 'ignore': True} yaml.dump(cached_info, stream=output) print ('new version available: MicroDrop v{} (not installing ' 'now)'.format(latest_version)) except: logger.error('Error caching latest version.', exc_info=True) elif response == gtk.RESPONSE_YES: # User selected `Yes`, so upgrade MicroDrop, but restrict upgrade # to within the same major version. try: major_version = int(cre_version.match(installed_version) .group('major')) install_log_json = ch.conda_exec('install', '--json', 'microdrop >={}, <{}' .format(major_version, major_version + 1)) install_response = json.loads(install_log_json) unlinked, linked = ch.install_info(install_response) print ch.format_install_info(unlinked, linked) try: # Remove stale cached MicroDrop version data. cached_path.remove() except: pass except: logger.error('Error upgrading MicroDrop.', exc_info=True)