Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add new extension manager API #248

Merged
merged 30 commits into from
Jul 31, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
43c9e9f
add utils module in extension
Zsailer Jun 10, 2020
d8ad891
wip
Zsailer Jun 11, 2020
5eb75f9
wip
Zsailer Jun 11, 2020
28ac361
add extension manager
Zsailer Jun 12, 2020
34f9969
remove debugging lines
Zsailer Jun 12, 2020
fb90ad4
enable linking as a way to add server config
Zsailer Jun 12, 2020
3f315ea
switch to traitlets object
Zsailer Jun 18, 2020
bd47f7e
fix some misused validation methods in traits
Zsailer Jun 22, 2020
fd3b27f
add extension manager tests
Zsailer Jun 25, 2020
ed3317c
drop string literal for python 3.5
Zsailer Jun 25, 2020
416c428
remove commented lines
Zsailer Jun 25, 2020
b3450f5
replace ModuleNotFoundError with ImportError for py35
Zsailer Jun 25, 2020
e8e0f94
attempt to address naming issues in extension loader
Zsailer Jun 26, 2020
a86e123
pass serverapp to extension manager methods
Zsailer Jun 26, 2020
9027baf
change example back to orignal name
Zsailer Jun 26, 2020
a790cf4
enable extensionapps to configure the server when launched directly
Zsailer Jun 26, 2020
feb052b
track linking of extension to prevent duplicate loads
Zsailer Jul 9, 2020
8e8f8a4
minor changes to logging in extension manager
Zsailer Jul 17, 2020
058db71
remove custom handler from server
Zsailer Jul 17, 2020
9ac399f
remove unused jupyter paths logic
Zsailer Jul 17, 2020
9ac50db
update tests with changes in extension manager
Zsailer Jul 23, 2020
dadc124
fixed subtle bug in extension manager classes—accidently used class a…
Zsailer Jul 27, 2020
f6c3cad
handle sorting of dictionary keys properly
Zsailer Jul 27, 2020
0003ade
rename extension_paths to extension_points and add logging to reflect…
Zsailer Jul 27, 2020
3b09860
update tests to changes in metadata function
Zsailer Jul 27, 2020
cbe843b
add a basic extension config manager
Zsailer Jul 28, 2020
eca9e43
leverage the extension manager for enabling/disabling
Zsailer Jul 31, 2020
1ccf97b
list all paths in server extension application
Zsailer Jul 31, 2020
b0a759f
minor styling fix
Zsailer Jul 31, 2020
4d1f8e5
remove unused imports
Zsailer Jul 31, 2020
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions examples/simple/pyproject.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
[build-system]
requires = ["jupyter_packaging~=0.5.0", "setuptools>=40.8.0", "wheel"]
build-backend = "setuptools.build_meta"
28 changes: 19 additions & 9 deletions examples/simple/setup.py
Original file line number Diff line number Diff line change
@@ -1,40 +1,46 @@
import os, setuptools
from setuptools import find_packages
import os
from setuptools import setup
from jupyter_packaging import create_cmdclass


VERSION = '0.0.1'


def get_data_files():
"""Get the data files for the package.
"""
data_files = [
('etc/jupyter/jupyter_server_config.d', ['etc/jupyter/jupyter_server_config.d/simple_ext1.json']),
('etc/jupyter/jupyter_server_config.d', ['etc/jupyter/jupyter_server_config.d/simple_ext2.json']),
('etc/jupyter/jupyter_server_config.d', ['etc/jupyter/jupyter_server_config.d/simple_ext11.json']),
('etc/jupyter/jupyter_server_config.d', 'etc/jupyter/jupyter_server_config.d/', '*.json'),
]
def add_data_files(path):
for (dirpath, dirnames, filenames) in os.walk(path):
if filenames:
data_files.append((dirpath, [os.path.join(dirpath, filename) for filename in filenames]))
paths = [(dirpath, dirpath, filename) for filename in filenames]
data_files.extend(paths)
# Add all static and templates folders.
add_data_files('simple_ext1/static')
add_data_files('simple_ext1/templates')
add_data_files('simple_ext2/static')
add_data_files('simple_ext2/templates')
return data_files

setuptools.setup(

cmdclass = create_cmdclass(
data_files_spec=get_data_files()
)

setup_args = dict(
name = 'jupyter_server_example',
version = VERSION,
description = 'Jupyter Server Example',
long_description = open('README.md').read(),
packages = find_packages(),
python_requires = '>=3.5',
install_requires = [
'jupyter_server',
'jinja2',
],
include_package_data=True,
data_files = get_data_files(),
cmdclass = cmdclass,
entry_points = {
'console_scripts': [
'jupyter-simple-ext1 = simple_ext1.application:main',
Expand All @@ -43,3 +49,7 @@ def add_data_files(path):
]
},
)


if __name__ == '__main__':
setup(**setup_args)
1 change: 0 additions & 1 deletion jupyter_server/config_manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -118,7 +118,6 @@ def set(self, section_name, data):
# Generate the JSON up front, since it could raise an exception,
# in order to avoid writing half-finished corrupted data to disk.
json_content = json.dumps(data, indent=2)

if PY3:
f = io.open(filename, 'w', encoding='utf-8')
else:
Expand Down
69 changes: 47 additions & 22 deletions jupyter_server/extension/application.py
Original file line number Diff line number Diff line change
Expand Up @@ -144,12 +144,27 @@ class method. This method can be set as a entry_point in
# side-by-side when launched directly.
load_other_extensions = True

# A useful class property that subclasses can override to
# configure the underlying Jupyter Server when this extension
# is launched directly (using its `launch_instance` method).
serverapp_config = {
"open_browser": True
}

# The extension name used to name the jupyter config
# file, jupyter_{name}_config.
# This should also match the jupyter subcommand used to launch
# this extension from the CLI, e.g. `jupyter {name}`.
name = None

@classmethod
def get_extension_package(cls):
return cls.__module__.split('.')[0]

@classmethod
def get_extension_point(cls):
return cls.__module__

# Extension URL sets the default landing page for this extension.
extension_url = "/"

Expand All @@ -158,6 +173,9 @@ class method. This method can be set as a entry_point in
ServerApp,
]

# A ServerApp is not defined yet, but will be initialized below.
serverapp = None

@property
def static_url_prefix(self):
return "/static/{name}/".format(
Expand Down Expand Up @@ -281,28 +299,17 @@ def _prepare_templates(self):
self.initialize_templates()

@classmethod
def initialize_server(cls, argv=[], load_other_extensions=True, **kwargs):
"""Creates an instance of ServerApp where this extension is enabled
(superceding disabling found in other config from files).

This is necessary when launching the ExtensionApp directly from
the `launch_instance` classmethod.
"""
# The ExtensionApp needs to add itself as enabled extension
# to the jpserver_extensions trait, so that the ServerApp
# initializes it.
config = Config({
def _jupyter_server_config(cls):
base_config = {
"ServerApp": {
"jpserver_extensions": {cls.name: True},
"open_browser": cls.open_browser,
"jpserver_extensions": {cls.get_extension_package(): True},
"default_url": cls.extension_url
}
})
serverapp = ServerApp.instance(**kwargs, argv=[], config=config)
serverapp.initialize(argv=argv, find_extensions=load_other_extensions)
return serverapp
}
base_config["ServerApp"].update(cls.serverapp_config)
return base_config

def link_to_serverapp(self, serverapp):
def _link_jupyter_server_extension(self, serverapp):
"""Link the ExtensionApp to an initialized ServerApp.

The ServerApp is stored as an attribute and config
Expand All @@ -315,7 +322,7 @@ def link_to_serverapp(self, serverapp):
# Load config from an ExtensionApp's config files.
self.load_config_file()
# ServerApp's config might have picked up
# CLI config for the ExtensionApp. We call
# config for the ExtensionApp. We call
# update_config to update ExtensionApp's
# traits with these values found in ServerApp's
# config.
Expand All @@ -330,6 +337,22 @@ def link_to_serverapp(self, serverapp):
# i.e. ServerApp traits <--- ExtensionApp config
self.serverapp.update_config(self.config)

@classmethod
def initialize_server(cls, argv=[], load_other_extensions=True, **kwargs):
"""Creates an instance of ServerApp where this extension is enabled
(superceding disabling found in other config from files).

This is necessary when launching the ExtensionApp directly from
the `launch_instance` classmethod.
"""
# The ExtensionApp needs to add itself as enabled extension
# to the jpserver_extensions trait, so that the ServerApp
# initializes it.
config = Config(cls._jupyter_server_config())
serverapp = ServerApp.instance(**kwargs, argv=[], config=config)
serverapp.initialize(argv=argv, find_extensions=load_other_extensions)
return serverapp

def initialize(self):
"""Initialize the extension app. The
corresponding server app and webapp should already
Expand All @@ -341,7 +364,7 @@ def initialize(self):
3) Points Tornado Webapp to templates and
static assets.
"""
if not hasattr(self, 'serverapp'):
if not self.serverapp:
msg = (
"This extension has no attribute `serverapp`. "
"Try calling `.link_to_serverapp()` before calling "
Expand Down Expand Up @@ -374,12 +397,14 @@ def _load_jupyter_server_extension(cls, serverapp):
"""Initialize and configure this extension, then add the extension's
settings and handlers to the server's web application.
"""
extension_manager = serverapp.extension_manager
try:
# Get loaded extension from serverapp.
extension = serverapp._enabled_extensions[cls.name]
point = extension_manager.extension_points[cls.name]
extension = point.app
except KeyError:
extension = cls()
extension.link_to_serverapp(serverapp)
extension._link_jupyter_server_extension(serverapp)
extension.initialize()
return extension

Expand Down
57 changes: 57 additions & 0 deletions jupyter_server/extension/config.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@

from jupyter_server.services.config.manager import ConfigManager


DEFAULT_SECTION_NAME = "jupyter_server_config"


class ExtensionConfigManager(ConfigManager):
"""A manager class to interface with Jupyter Server Extension config
found in a `config.d` folder. It is assumed that all configuration
files in this directory are JSON files.
"""
def get_jpserver_extensions(
self,
section_name=DEFAULT_SECTION_NAME
):
"""Return the jpserver_extensions field from all
config files found."""
data = self.get(section_name)
return (
data
.get("ServerApp", {})
.get("jpserver_extensions", {})
)

def enabled(
self,
name,
section_name=DEFAULT_SECTION_NAME,
include_root=True
):
"""Is the extension enabled?"""
extensions = self.get_jpserver_extensions(section_name)
try:
return extensions[name]
except KeyError:
return False

def enable(self, name):
data = {
"ServerApp": {
"jpserver_extensions": {
name: True
}
}
}
self.update(name, data)

def disable(self, name):
data = {
"ServerApp": {
"jpserver_extensions": {
name: False
}
}
}
self.update(name, data)
Loading