| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141 |
- """
- This module contains a Jupyter Server extension that attempts to
- make classic server and notebook extensions work in the new server.
- Unfortunately, you'll notice that requires some major monkey-patching.
- The goal is that this extension will only be used as a temporary
- patch to transition extension authors from classic notebook server to jupyter_server.
- """
- import os
- import types
- import inspect
- from functools import wraps
- from jupyter_core.paths import jupyter_config_path
- from traitlets.traitlets import is_trait
- from jupyter_server.services.config.manager import ConfigManager
- from .traits import NotebookAppTraits
- class ClassProxyError(Exception):
- pass
- def proxy(obj1, obj2, name, overwrite=False):
- """Redirects a method, property, or trait from object 1 to object 2."""
- if hasattr(obj1, name) and overwrite is False:
- raise ClassProxyError(
- "Cannot proxy the attribute '{name}' from {cls2} because "
- "{cls1} already has this attribute.".format(
- name=name,
- cls1=obj1.__class__,
- cls2=obj2.__class__
- )
- )
- attr = getattr(obj2, name)
- # First check if this thing is a trait (see traitlets)
- cls_attr = getattr(obj2.__class__, name)
- if is_trait(cls_attr) or type(attr) == property:
- thing = property(lambda self: getattr(obj2, name))
- elif isinstance(attr, types.MethodType):
- @wraps(attr)
- def thing(self, *args, **kwargs):
- return attr(*args, **kwargs)
- # Anything else appended on the class is just an attribute of the class.
- else:
- thing = attr
- setattr(obj1.__class__, name, thing)
- def public_members(obj):
- members = inspect.getmembers(obj)
- return [m for m, _ in members if not m.startswith('_')]
- def diff_members(obj1, obj2):
- """Return all attribute names found in obj2 but not obj1"""
- m1 = public_members(obj1)
- m2 = public_members(obj2)
- return set(m2).difference(m1)
- def get_nbserver_extensions(config_dirs):
- cm = ConfigManager(read_config_path=config_dirs)
- section = cm.get("jupyter_notebook_config")
- extensions = section.get('NotebookApp', {}).get('nbserver_extensions', {})
- return extensions
- def _link_jupyter_server_extension(serverapp):
- # Get the extension manager from the server
- manager = serverapp.extension_manager
- logger = serverapp.log
- # Hack that patches the enabled extensions list, prioritizing
- # jupyter nbclassic. In the future, it would be much better
- # to incorporate a dependency injection system in the
- # Extension manager that allows extensions to list
- # their dependency tree and sort that way.
- def sorted_extensions(self):
- """Dictionary with extension package names as keys
- and an ExtensionPackage objects as values.
- """
- # Sort the keys and
- keys = sorted(self.extensions.keys())
- keys.remove("notebook_shim")
- keys = ["notebook_shim"] + keys
- return {key: self.extensions[key] for key in keys}
- manager.__class__.sorted_extensions = property(sorted_extensions)
- # Look to see if nbclassic is enabled. if so,
- # link the nbclassic extension here to load
- # its config. Then, port its config to the serverapp
- # for backwards compatibility.
- try:
- pkg = manager.extensions["notebook_shim"]
- pkg.link_point("notebook_shim", serverapp)
- point = pkg.extension_points["notebook_shim"]
- nbapp = point.app
- except Exception:
- nbapp = NotebookAppTraits()
- # Proxy NotebookApp traits through serverapp to notebookapp.
- members = diff_members(serverapp, nbapp)
- for m in members:
- proxy(serverapp, nbapp, m)
- # Find jupyter server extensions listed as notebook server extensions.
- jupyter_paths = jupyter_config_path()
- config_dirs = jupyter_paths + [serverapp.config_dir]
- nbserver_extensions = get_nbserver_extensions(config_dirs)
- # Link all extensions found in the old locations for
- # notebook server extensions.
- for name, enabled in nbserver_extensions.items():
- # If the extension is already enabled in the manager, i.e.
- # because it was discovered already by Jupyter Server
- # through its jupyter_server_config, then don't re-enable here.
- if name not in manager.extensions:
- successful = manager.add_extension(name, enabled=enabled)
- if successful:
- logger.info(
- "{name} | extension was found and enabled by notebook_shim. "
- "Consider moving the extension to Jupyter Server's "
- "extension paths.".format(name=name)
- )
- manager.link_extension(name)
- def _load_jupyter_server_extension(serverapp):
- # Patch the config service manager to find the
- # proper path for old notebook frontend extensions
- config_manager = serverapp.config_manager
- read_config_path = config_manager.read_config_path
- read_config_path += [os.path.join(p, 'nbconfig')
- for p in jupyter_config_path()]
- config_manager.read_config_path = read_config_path
|