You cannot select more than 25 topics
			Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
		
		
		
		
		
			
		
			
				
	
	
		
			142 lines
		
	
	
		
			5.1 KiB
		
	
	
	
		
			Python
		
	
			
		
		
	
	
			142 lines
		
	
	
		
			5.1 KiB
		
	
	
	
		
			Python
		
	
"""
 | 
						|
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
 |