Example #1
0
def install(enable=False,
            disable=False,
            status=None,
            prefix=None,
            path=None,
            verbose=False):
    """Installs the nb_conda_kernels configuration data.

    Parameters
    ----------
    enable: bool
        Enable nb_conda_kernels; that is, make the changes to
        the Jupyter notebook configuration so that it will is
        available for Jupyter notebooks.
    disable: bool
        Disable nb_conda_kernels.
    status: bool
        Print the installation status, but make no changes.
    Exactly one of enable/disable/status must be supplied.

    verbose: bool
        If true, print more verbose output during operation.

    prefix: None
        The prefix of the Python environment where the Jupyter
        configuration is to be created and/or modified. It is
        equivalent to supplying
            path = join(prefix, 'etc', 'jupyter')
    path: None
        The directory where the Jupyter configuration file is
        to be created and/or modified. The name of the file is
        hardcoded to jupyter_notebook_config.json.
    Either prefix or path may be supplied, but not both. If
    neither is supplied, then the first path found in
        jupyter_core_paths.jupyter_config_path()
    whose directory is within sys.prefix will be selected. If
    there is no such path, the first path will be selected.
    """
    if verbose:
        log.setLevel(logging.DEBUG)
    verbose = log.getEffectiveLevel() == logging.DEBUG
    if status:
        log.info("Determining the status of nb_conda_kernels...")
    else:
        log.info("{}ing nb_conda_kernels...".format(
            ENDIS[enable][:-2].capitalize()))
    log.info("CONDA_PREFIX: {}".format(sys.prefix))

    is_enabled_entry = False
    # Disable the entry-point based mechanism. Most if this code will need
    # to be removed, because the kernel discovery mechanism was changed
    # before jupyter_client 6 was released. For now we're dropping back to
    # the jupyter_client 5 model until we leverage the new mechanism.
    has_entrypoints = False  # int(jc_version.split('.', 1)[0]) >= 6
    log.debug('Entry points:')
    for ep in iter_entry_points(group=JCKP):
        log.debug('  - {}'.format(ep))
        if str(ep).split('=', 1)[-1].strip() == NCKDCKP:
            is_enabled_entry = True
    if not is_enabled_entry and has_entrypoints:
        log.error(('NOTE: nb_conda_kernels is missing its entry point '
                   'for jupyter_client.kernel_providers, which is needed '
                   'for correct operation this version of Jupyter.'))
    if is_enabled_entry and not has_entrypoints:
        log.debug(
            '  NOTE: entry points not used in Jupyter {}'.format(jc_version))
        is_enabled_entry = False

    all_paths = [abspath(p) for p in jupyter_config_path()]
    default_path = join(sys.prefix, 'etc', 'jupyter')
    search_paths = all_paths[::-1]
    if path or prefix:
        if prefix:
            path = join(prefix, 'etc', 'jupyter')
        path = abspath(path)
    else:
        prefix_s = sys.prefix + os.sep
        for path in search_paths:
            if path.startswith(prefix_s):
                break
        else:
            path = default_path
    if path != default_path or path not in all_paths:
        log.info("Target path: {}".format(path))
    if path not in all_paths:
        log.warning('WARNING: the configuration for the current environment\n'
                    'is not affected by the target configuration path.')
        search_paths.append(path)

    # Determine the effective configuration by going through the search path
    # in reverse order. Moving forward we will be modifying only the JupyterApp
    # key in the jupyter_config.json file. However for legacy reasons we are
    # also looking at NotebookApp keys and the jupyter_notebook_config.json file,
    # and cleaning those out as we can.
    log.debug('Configuration files:')
    fpaths = set()
    is_enabled_all = {}
    is_enabled_local = {}
    for path_g in search_paths:
        flag = '-' if path != path_g else ('*' if path in all_paths else 'x')
        value = ''
        for fbase in (JC, JNC):
            fpath = join(path_g, fbase + '.json')
            cfg = BaseJSONConfigManager(config_dir=path_g).get(fbase)
            dirty = False
            for key in (JA, NBA):
                spec = cfg.get(key, {}).get(KSMC)
                if status or path_g != path:
                    # No changes in status mode, or if we're not in the target path
                    expected = spec
                elif enable and fbase == JC and key == JA and not is_enabled_entry:
                    # Add the spec if we are enabling, the entry point is not active,
                    # and we're using the new file (jupyter_config.json) and key (JupyterApp)
                    expected = CKSM
                else:
                    # In all other cases, clear the spec out for cleanup
                    expected = None
                if spec != expected:
                    if expected is None:
                        cfg[key].pop(KSMC)
                        if not cfg[key]:
                            cfg.pop(key)
                    else:
                        cfg.setdefault(key, {})[KSMC] = expected
                    spec = expected
                    dirty = True
                if spec:
                    if path_g in all_paths:
                        is_enabled_all[key] = spec == CKSM
                    if path_g == path:
                        is_enabled_local[key] = spec == CKSM
                    else:
                        fpaths.add(join(path_g, fbase + '.json'))
            if dirty:
                BaseJSONConfigManager(config_dir=path).set(fbase, cfg)
            if dirty or exists(fpath):
                value += '\n      ' + fbase + '.json'
                if dirty:
                    value += ' (MODIFIED)'
                value += ': '
                value += '\n        '.join(
                    json.dumps(cfg, indent=2).splitlines())
        log.debug('  {} {}: {}'.format(flag, shorten(path_g), value
                                       or '<no files>'))
    is_enabled_all = bool(is_enabled_all.get(NBA, is_enabled_all.get(JA)))
    is_enabled_local = bool(is_enabled_local.get(NBA,
                                                 is_enabled_local.get(JA)))

    if is_enabled_all != is_enabled_local or (is_enabled_entry and disable):
        sev = 'WARNING' if status else 'ERROR'
        if is_enabled_entry and disable:
            msg = [
                '{}: the entrypoint mechanism cannot be disabled'.format(sev),
                'with changes to jupyter_config.json. To disable it,',
                'remove the nb_conda_kernels package.'
            ]
            fpaths = []
        elif path not in all_paths:
            msg = fpaths = []
        elif status:
            msg = [
                '{}: the local configuration of nb_conda_kernels'.format(sev),
                'conflicts with the global configuration. Please examine'
            ]
        else:
            what = ENDIS[is_enabled_local][:-1]
            msg = [
                '{}: the attempt to {} nb_conda_kernels failed due to'.format(
                    sev, what),
                'conflicts with global configuration files. Please examine'
            ]
        if fpaths:
            msg.append('the following file{} for potential conflicts:'.format(
                's' if len(fpaths) > 1 else ''))
            for fpath in fpaths:
                msg.append('    ' + shorten(fpath, False))
            if not verbose:
                msg.append('Use the --verbose flag for more information.')
        if msg:
            (log.warning if status else log.error)('\n'.join(msg))

    is_enabled_all = is_enabled_all or is_enabled_entry
    log.info('Status: {}'.format(ENDIS[is_enabled_all]))
    return 0 if status or is_enabled_all == is_enabled_local else 1
Example #2
0
def install(enable=False,
            disable=False,
            status=None,
            prefix=None,
            path=None,
            verbose=False):
    """Installs the nb_conda_kernels configuration data.

    Parameters
    ----------
    enable: bool
        Enable nb_conda_kernels; that is, make the changes to
        the Jupyter notebook configuration so that it will is
        available for Jupyter notebooks.
    disable: bool
        Disable nb_conda_kernels.
    status: bool
        Print the installation status, but make no changes.
    Exactly one of enable/disable/status must be supplied.

    verbose: bool
        If true, print more verbose output during operation.

    prefix: None
        The prefix of the Python environment where the Jupyter
        configuration is to be created and/or modified. It is
        equivalent to supplying
            path = join(prefix, 'etc', 'jupyter')
    path: None
        The directory where the Jupyter configuration file is
        to be created and/or modified. The name of the file is
        hardcoded to jupyter_notebook_config.json.
    Either prefix or path may be supplied, but not both. If
    neither is supplied, then the first path found in
        jupyter_core_paths.jupyter_config_path()
    whose directory is within sys.prefix will be selected. If
    there is no such path, the first path will be selected.
    """
    if verbose:
        log.setLevel(logging.DEBUG)
    verbose = log.getEffectiveLevel() == logging.DEBUG
    if status:
        log.info("Determining the status of nb_conda_kernels...")
    else:
        log.info("{}ing nb_conda_kernels...".format(
            ENDIS[enable][:-2].capitalize()))

    is_enabled_entry = False
    has_entrypoints = int(jc_version.split('.', 1)[0]) >= 6
    log.debug('Entry points:')
    for ep in iter_entry_points(group=JCKP):
        log.debug('  - {}'.format(ep))
        if str(ep).split('=', 1)[-1].strip() == NCKDCKP:
            is_enabled_entry = True
    if not is_enabled_entry and has_entrypoints:
        log.error(('NOTE: nb_conda_kernels is missing its entry point '
                   'for jupyter_client.kernel_providers, which is needed '
                   'for correct operation with Jupyter 6.0.'))
    if is_enabled_entry and not has_entrypoints:
        log.debug(
            '  NOTE: entry points not used in Jupyter {}'.format(jc_version))
        is_enabled_entry = False

    all_paths = jupyter_config_path()
    if path or prefix:
        if prefix:
            path = join(prefix, 'etc', 'jupyter')
        if path not in all_paths:
            log.warn('WARNING: the requested path\n    {}\n'
                     'is not on the Jupyter config path'.format(path))
    else:
        prefix_s = sys.prefix + os.sep
        for path in all_paths[::-1]:
            if path.startswith(prefix_s):
                break
        else:
            log.warn('WARNING: no path within sys.prefix was found')

    cfg = BaseJSONConfigManager(config_dir=path).get(JNC)
    is_enabled_local = cfg.get(NBA, {}).get(KSMC, None) == CKSM

    if not status and is_enabled_local != (enable and not is_enabled_entry):
        if enable:
            log.debug('Adding to local configuration')
            cfg.setdefault(NBA, {})[KSMC] = CKSM
        else:
            log.debug('Removing from local configuration')
            cfg[NBA].pop(KSMC)
            if not cfg[NBA]:
                cfg.pop(NBA)
        log.debug("Writing config in {}...".format(path))
        BaseJSONConfigManager(config_dir=path).set(JNC, cfg)
        is_enabled_local = enable

    # Retrieve the global configuration the same way that the Notebook
    # app does: by looking through jupyter_notebook_config.json in
    # every directory in jupyter_config_path(), in reverse order.
    all_paths = jupyter_config_path()
    log.debug('{} entries:'.format(JNCJ))
    is_enabled_all = False
    search_paths = all_paths[::-1]
    if path not in all_paths:
        search_paths.append(path)
    for path_g in search_paths:
        cfg_g = BaseJSONConfigManager(config_dir=path_g).get(JNC)
        flag = '-' if path != path_g else ('*' if path in all_paths else 'x')
        if exists(join(path_g, JNCJ)):
            value = '\n    '.join(json.dumps(cfg_g, indent=2).splitlines())
            if NBA in cfg_g and KSMC in cfg_g[NBA]:
                is_enabled_all = cfg_g[NBA][KSMC] == CKSM
        else:
            value = '<no file>'
        log.debug('  {} {}: {}'.format(flag, path_g, value))

    if is_enabled_all != is_enabled_local:
        logsev = log.warn if status else log.error
        logstr = 'WARNING' if status else 'ERROR'
        mode_g = ENDIS[is_enabled_all].upper()
        mode_l = ENDIS[is_enabled_local].upper()
        logsev(('{}: The global setting does not match the local setting:\n'
                '    Global: {}\n'
                '    Local:  {}\n'
                'This is typically caused by another configuration file in\n'
                'the path with a conflicting setting.').format(
                    logstr, mode_g, mode_l))
        if not verbose:
            logsev("Use the --verbose flag for more information.")
        if not status:
            return 1

    is_enabled_all = is_enabled_all or (has_entrypoints and is_enabled_entry)
    log.info('Status: {}'.format(ENDIS[is_enabled_all]))
    return 0