From f3074fde938186ce06f3164bb4a37ec1d5cffd67 Mon Sep 17 00:00:00 2001 From: Nicholas Bollweg Date: Wed, 9 Mar 2016 17:15:38 -0500 Subject: [PATCH] logging, removing redundant implementations --- notebook/nbextensions.py | 164 +++++++++++++++++------- notebook/serverextensions.py | 97 +++++++++++--- notebook/tests/test_serverextensions.py | 2 +- 3 files changed, 199 insertions(+), 64 deletions(-) diff --git a/notebook/nbextensions.py b/notebook/nbextensions.py index 0c810bbe6..06fd313f1 100644 --- a/notebook/nbextensions.py +++ b/notebook/nbextensions.py @@ -42,8 +42,8 @@ DEPRECATED_ARGUMENT = object() NBCONFIG_SECTIONS = ['common', 'notebook', 'tree', 'edit', 'terminal'] -GREEN_OK = '\033[32m OK \033[0m' if os.name != 'nt' else 'enabled ' -RED_X = '\033[31m X \033[0m' if os.name != 'nt' else 'disabled' +GREEN_OK = '\033[32mOK\033[0m' if os.name != 'nt' else 'ok' +RED_X = '\033[31m X\033[0m' if os.name != 'nt' else ' X' #------------------------------------------------------------------------------ # Public API @@ -213,8 +213,7 @@ def install_nbextension(path, overwrite=False, symlink=False, def install_nbextension_python(package, overwrite=False, symlink=False, - user=False, sys_prefix=False, prefix=None, nbextensions_dir=None, - logger=None): + user=False, sys_prefix=False, prefix=None, nbextensions_dir=None, logger=None): """Install an nbextension bundled in a Python package. See install_nbextension for parameter information.""" @@ -225,7 +224,7 @@ def install_nbextension_python(package, overwrite=False, symlink=False, dest = nbext['dest'] require = nbext['require'] if logger: - logger.info("%s %s %s" % (src, dest, require)) + logger.info("Installing %s -> %s" % (src, dest)) full_dest = install_nbextension( src, overwrite=overwrite, symlink=symlink, user=user, sys_prefix=sys_prefix, prefix=prefix, nbextensions_dir=nbextensions_dir, @@ -281,6 +280,7 @@ def uninstall_nbextension(dest, require, user=False, sys_prefix=False, prefix=No for section in NBCONFIG_SECTIONS: cm.update(section, {"load_extensions": {require: None}}) + def uninstall_nbextension_python(package, user=False, sys_prefix=False, prefix=None, nbextensions_dir=None, logger=None): @@ -290,32 +290,97 @@ def uninstall_nbextension_python(package, dest = nbext['dest'] require = nbext['require'] if logger: - logger.info("{} {}".format(dest, require)) + logger.info("Uninstalling {} {}".format(dest, require)) uninstall_nbextension(dest, require, user=user, sys_prefix=sys_prefix, prefix=prefix, nbextensions_dir=nbextensions_dir, logger=logger) -def _set_nbextension_state_python(state, package, user, sys_prefix): + +def _set_nbextension_state(section, require, state, user, sys_prefix, + logger=None): + config_dir = os.path.join( + _get_config_dir(user=user, sys_prefix=sys_prefix), 'nbconfig') + cm = BaseJSONConfigManager(config_dir=config_dir) + if logger: + logger.info("{} {} extension {}...".format( + "Enabling" if state else "Disabling", + section, + require + )) + cm.update(section, {"load_extensions": {require: state}}) + + +def _set_nbextension_state_python(state, package, user, sys_prefix, + logger=None): """ Enable or disable a nbextension """ m, nbexts = _get_nbextension_metadata(package) - config_dir = os.path.join(_get_config_dir(user=user, sys_prefix=sys_prefix), 'nbconfig') - cm = BaseJSONConfigManager(config_dir=config_dir) - for nbext in nbexts: - cm.update(nbext['section'], {"load_extensions": {nbext['require']: state}}) + return [_set_nbextension_state(section=nbext["section"], + require=nbext["require"], + state=state, + user=user, sys_prefix=sys_prefix, + logger=logger) + for nbext in nbexts] + + +def enable_nbextension(section, require, user, sys_prefix, logger=None): + return _set_nbextension_state(section=section, require=require, + state=True, + user=user, sys_prefix=sys_prefix, + logger=logger) + + +def disable_nbextension(section, require, user, sys_prefix, logger=None): + return _set_nbextension_state(section=section, require=require, + state=False, + user=user, sys_prefix=sys_prefix, + logger=logger) -def enable_nbextension_python(package, user=False, sys_prefix=False): + +def enable_nbextension_python(package, user=False, sys_prefix=False, + logger=None): """Enable an nbextension associated with a Python package.""" - _set_nbextension_state_python(True, package, user, sys_prefix) - + return _set_nbextension_state_python(True, package, user, sys_prefix, + logger=logger) -def disable_nbextension_python(package, user=False, sys_prefix=False): + +def disable_nbextension_python(package, user=False, sys_prefix=False, + logger=None): """Disable an nbextension associated with a Python package.""" - _set_nbextension_state_python(False, package, user, sys_prefix) + return _set_nbextension_state_python(False, package, user, sys_prefix, + logger=logger) +def validate_nbextension(require, logger=None): + warnings = [] + infos = [] + + js_exists = False + for exts in _nbextension_dirs(): + # Does the Javascript entrypoint actually exist on disk? + js = "{}.js".format(os.path.join(exts, *require.split("/"))) + js_exists = os.path.exists(js) + if js_exists: + break + + require_tmpl = "- require? {} {}" + if js_exists: + infos.append(require_tmpl.format(GREEN_OK, require)) + else: + warnings.append(require_tmpl.format(RED_X, require)) + + if logger: + if warnings: + logger.warn("- Validating: problems found:") + map(logger.warn, warnings) + map(logger.info, infos) + else: + logger.info("- Validating: {}".format(GREEN_OK)) + + return warnings + def validate_nbextension_python(spec, full_dest, logger=None): - """Assess the health of a (to be) installed nbextension + """Assess the health of an installed nbextension Parameters ---------- @@ -332,26 +397,26 @@ def validate_nbextension_python(spec, full_dest, logger=None): section = spec.get("section", None) if section in NBCONFIG_SECTIONS: - infos.append("{} section: {}".format(GREEN_OK, section)) + infos.append(" {} section: {}".format(GREEN_OK, section)) else: - warnings.append("{} section: {}".format(RED_X, section)) + warnings.append(" {} section: {}".format(RED_X, section)) require = spec.get("require", None) if require is not None: require_path = os.path.join(full_dest, "{}.js".format(require)) if os.path.exists(require_path): - infos.append("{} require: {}".format(GREEN_OK, require_path)) + infos.append(" {} require: {}".format(GREEN_OK, require_path)) else: - warnings.append("{} require: {}".format(RED_X, require_path)) + warnings.append(" {} require: {}".format(RED_X, require_path)) if logger: if warnings: - logger.warn("Validating: problems found:") + logger.warn("- Validating: problems found:") [logger.warn(warning) for warning in warnings] [logger.info(info) for info in infos] logger.warn("Full spec: {}".format(spec)) else: - logger.info("Validating: {}".format(GREEN_OK)) + logger.info("- Validating: {}".format(GREEN_OK)) return infos, warnings @@ -379,7 +444,7 @@ _base_flags = { "BaseNBExtensionApp" : { "python" : True, }}, "Install from a Python package" - ), + ) } _base_flags['python'] = _base_flags['py'] @@ -461,7 +526,9 @@ class InstallNBExtensionApp(BaseNBExtensionApp): def install_extensions(self): if len(self.extra_args)>1: raise ValueError("only one nbextension allowed at a time. Call multiple times to install multiple extensions.") + install = install_nbextension_python if self.python else install_nbextension + install(self.extra_args[0], overwrite=self.overwrite, symlink=self.symlink, @@ -555,27 +622,23 @@ class ToggleNBExtensionApp(BaseNBExtensionApp): def _config_file_name_default(self): return 'jupyter_notebook_config' - - def _toggle_nbextension(self, section, require): - config_dir = os.path.join(_get_config_dir(user=self.user, sys_prefix=self.sys_prefix), 'nbconfig') - cm = BaseJSONConfigManager(parent=self, config_dir=config_dir) - if self._toggle_value is None and require not in cm.get(section).get('load_extensions', {}): - sys.exit('{} is not enabled in section {}'.format(require, section)) - # We're using a dict as a set - updating with None removes the key - cm.update(section, {"load_extensions": {require: self._toggle_value}}) def toggle_nbextension_python(self, package): - m, nbexts = _get_nbextension_metadata(package) - for nbext in nbexts: - section = nbext['section'] - require = nbext['require'] - self._toggle_nbextension(section, require) + toggle = (enable_nbextension_python if self._toggle_value + else disable_nbextension_python) + return toggle(package, + user=self.user, + sys_prefix=self.sys_prefix, + logger=self.log) def toggle_nbextension(self, require): - self._toggle_nbextension(self.section, require) + toggle = (enable_nbextension if self._toggle_value + else disable_nbextension) + return toggle(self.section, require, + user=self.user, sys_prefix=self.sys_prefix, + logger=self.log) def start(self): - if not self.extra_args: sys.exit('Please specify an nbextension/package to enable or disable') elif len(self.extra_args) > 1: @@ -608,6 +671,7 @@ class ListNBExtensionsApp(BaseNBExtensionApp): def list_nbextensions(self): config_dirs = [os.path.join(p, 'nbconfig') for p in jupyter_config_path()] + for config_dir in config_dirs: self.log.info('config dir: {}'.format(config_dir)) cm = BaseJSONConfigManager(parent=self, config_dir=config_dir) @@ -616,9 +680,12 @@ class ListNBExtensionsApp(BaseNBExtensionApp): if 'load_extensions' in data: self.log.info(' {} section'.format(section)) - load_extensions = data['load_extensions'] - for x in load_extensions: - self.log.info(' {1} {0}'.format(x, GREEN_ENABLED if load_extensions[x] else RED_DISABLED)) + for require, enabled in data['load_extensions'].items(): + self.log.info(' {} {}'.format( + require, + GREEN_ENABLED if enabled else RED_DISABLED)) + if enabled: + validate_nbextension(require, logger=self.log) def start(self): self.list_nbextensions() @@ -670,10 +737,10 @@ def _should_copy(src, dest, logger=None): # we add a fudge factor to work around a bug in python 2.x # that was fixed in python 3.x: http://bugs.python.org/issue12904 if logger: - logger.warn("%s is out of date" % dest) + logger.warn("Out of date: %s" % dest) return True if logger: - logger.info("%s is up to date" % dest) + logger.info("Up to date: %s" % dest) return False @@ -710,6 +777,15 @@ def _get_nbextension_dir(user=False, sys_prefix=False, prefix=None, nbextensions return nbext +def _nbextension_dirs(): + """The possible locations of nbextensions.""" + return [ + pjoin(jupyter_data_dir(), u'nbextensions'), + pjoin(ENV_JUPYTER_PATH[0], u'nbextensions'), + pjoin(SYSTEM_JUPYTER_PATH[0], 'nbextensions') + ] + + def _get_config_dir(user=False, sys_prefix=False): if user and sys_prefix: raise ArgumentConflict("Cannot specify more than one of user or sys_prefix") diff --git a/notebook/serverextensions.py b/notebook/serverextensions.py index 650f76c55..d0a4fab79 100644 --- a/notebook/serverextensions.py +++ b/notebook/serverextensions.py @@ -6,30 +6,31 @@ from __future__ import print_function - +import importlib import sys + from jupyter_core.paths import jupyter_config_path from ._version import __version__ from .nbextensions import ( BaseNBExtensionApp, _get_config_dir, - GREEN_ENABLED, RED_DISABLED + GREEN_ENABLED, RED_DISABLED, + GREEN_OK, RED_X, ) from traitlets import Bool from traitlets.config.manager import BaseJSONConfigManager + # ------------------------------------------------------------------------------ # Public API # ------------------------------------------------------------------------------ - - class ArgumentConflict(ValueError): pass def toggle_serverextension_python(import_name, enabled=None, parent=None, - user=False, sys_prefix=False): + user=False, sys_prefix=False, logger=None): """Toggle a server extension. By default, toggles the extension in the system-wide Jupyter configuration @@ -50,6 +51,8 @@ def toggle_serverextension_python(import_name, enabled=None, parent=None, sys_prefix : bool [default: False] Toggle in the current Python environment's configuration location (e.g. ~/.envs/my-env/etc/jupyter). + logger : Jupyter logger [optional] + Logger instance to use """ config_dir = _get_config_dir(user=user, sys_prefix=sys_prefix) cm = BaseJSONConfigManager(parent=parent, config_dir=config_dir) @@ -58,18 +61,70 @@ def toggle_serverextension_python(import_name, enabled=None, parent=None, cfg.setdefault("NotebookApp", {}) .setdefault("nbserver_extensions", {}) ) - if enabled: - server_extensions[import_name] = True - else: - if enabled is None: - if import_name not in server_extensions: - print("server extension not installed") - else: - server_extensions[import_name] = not server_extensions[import_name] + + old_enabled = server_extensions.get(import_name, None) + new_enabled = enabled if enabled is not None else not old_enabled + + if logger: + if new_enabled: + logger.info("Enabling: %s" % (import_name)) else: - server_extensions[import_name] = False + logger.info("Disabling: %s" % (import_name)) + + server_extensions[import_name] = new_enabled + + if logger: + logger.info("- Writing config: {}".format(config_dir)) + cm.update("jupyter_notebook_config", cfg) + if new_enabled: + validate_serverextension(import_name, logger) + + +def validate_serverextension(import_name, logger=None): + """Assess the health of an installed server extension + + Parameters + ---------- + + import_name : str + Importable Python module (dotted-notation) exposing the magic-named + `load_jupyter_server_extension` function + logger : Jupyter logger [optional] + Logger instance to use + """ + + warnings = [] + infos = [] + + func = None + + if logger: + logger.info(" - Validating...") + + try: + mod = importlib.import_module(import_name) + func = getattr(mod, 'load_jupyter_server_extension', None) + except Exception: + logger.warning("Error loading server extension %s", import_name) + + import_msg = " {} is {} importable?" + if func is not None: + infos.append(import_msg.format(GREEN_OK, import_name)) + else: + warnings.append(import_msg.format(RED_X, import_name)) + + post_mortem = " {} {} {}" + if logger: + if warnings: + [logger.info(info) for info in infos] + [logger.warn(warning) for warning in warnings] + else: + logger.info(post_mortem.format(import_name, "", GREEN_OK)) + + return not warnings + # ---------------------------------------------------------------------- # Applications @@ -107,9 +162,11 @@ class ToggleServerExtensionApp(BaseNBExtensionApp): user = Bool(False, config=True, help="Whether to do a user install") sys_prefix = Bool(False, config=True, help="Use the sys.prefix as the prefix") python = Bool(False, config=True, help="Install from a Python package") - + def toggle_server_extension(self, import_name): - toggle_serverextension_python(import_name, self._toggle_value, parent=self, user=self.user, sys_prefix=self.sys_prefix) + toggle_serverextension_python( + import_name, self._toggle_value, parent=self, user=self.user, + sys_prefix=self.sys_prefix, logger=self.log) def toggle_server_extension_python(self, package): m, server_exts = _get_server_extension_metadata(package) @@ -118,7 +175,6 @@ class ToggleServerExtensionApp(BaseNBExtensionApp): self.toggle_server_extension(module) def start(self): - if not self.extra_args: sys.exit('Please specify a server extension/package to enable or disable') for arg in self.extra_args: @@ -158,8 +214,11 @@ class ListServerExtensionsApp(BaseNBExtensionApp): data.setdefault("NotebookApp", {}) .setdefault("nbserver_extensions", {}) ) - for x in server_extensions: - self.log.info(' {1} {0}'.format(x, GREEN_ENABLED if server_extensions[x] else RED_DISABLED)) + for import_name, enabled in server_extensions.items(): + self.log.info(' {} {}'.format( + import_name, + GREEN_ENABLED if enabled else RED_DISABLED)) + validate_serverextension(import_name, self.log) def start(self): self.list_server_extensions() diff --git a/notebook/tests/test_serverextensions.py b/notebook/tests/test_serverextensions.py index 4e08bba02..b8c105622 100644 --- a/notebook/tests/test_serverextensions.py +++ b/notebook/tests/test_serverextensions.py @@ -12,7 +12,7 @@ class TestInstallServerExtension(TestCase): @staticmethod def _jupyter_server_extension_paths(): return [{ - 'require': '_mockdestination/index' + 'module': '_mockdestination/index' }] import sys