Skip to content

Commit

Permalink
Merge pull request #6372 from blink1073/check-app-dir
Browse files Browse the repository at this point in the history
Clean up handling of launch assets
  • Loading branch information
blink1073 committed May 21, 2019
2 parents fc81a14 + c2ac6b2 commit fd4e23b
Show file tree
Hide file tree
Showing 8 changed files with 122 additions and 51 deletions.
3 changes: 3 additions & 0 deletions docs/source/user/extensions.rst
Expand Up @@ -271,6 +271,9 @@ environment variable. If not specified, it will default to
``<sys-prefix>/share/jupyter/lab``, where ``<sys-prefix>`` is the
site-specific directory prefix of the current Python environment. You
can query the current application path by running ``jupyter lab path``.
Note that the application directory is expected to contain the JupyterLab
static assets (e.g. `static/index.html`). If JupyterLab is launched
and the static assets are not present, it will display an error in the console and in the browser.

JupyterLab Build Process
^^^^^^^^^^^^^^^^^^^^^^^^
Expand Down
66 changes: 42 additions & 24 deletions jupyterlab/commands.py
Expand Up @@ -108,18 +108,34 @@ def dedupe_yarn(path, logger=None):
yarn_proc.wait()


def ensure_dev(logger=None):
"""Ensure that the dev assets are available.
def ensure_node_modules(cwd, logger=None):
"""Ensure that node_modules is up to date.
Returns true if the node_modules was updated.
"""
parent = pjoin(HERE, '..')
logger = _ensure_logger(logger)
yarn_proc = Process(['node', YARN_PATH, 'check', '--verify-tree'], cwd=cwd, logger=logger)
ret = yarn_proc.wait()

if not osp.exists(pjoin(parent, 'node_modules')):
yarn_proc = Process(['node', YARN_PATH], cwd=parent, logger=logger)
# Update node_modules if needed.
if ret != 0:
yarn_proc = Process(['node', YARN_PATH], cwd=cwd, logger=logger)
yarn_proc.wait()

dedupe_yarn(parent, logger)

if not osp.exists(pjoin(parent, 'dev_mode', 'static')):
return ret != 0


def ensure_dev(logger=None):
"""Ensure that the dev assets are available.
"""
parent = pjoin(HERE, '..')
logger = _ensure_logger(logger)
target = pjoin(parent, 'dev_mode', 'static')

# Determine whether to build.
if ensure_node_modules(parent, logger) or not osp.exists(target):
yarn_proc = Process(['node', YARN_PATH, 'build'], cwd=parent,
logger=logger)
yarn_proc.wait()
Expand All @@ -129,23 +145,30 @@ def ensure_core(logger=None):
"""Ensure that the core assets are available.
"""
staging = pjoin(HERE, 'staging')
logger = _ensure_logger(logger)

# Bail if the static directory already exists.
if osp.exists(pjoin(HERE, 'static')):
return

if not osp.exists(pjoin(staging, 'node_modules')):
yarn_proc = Process(['node', YARN_PATH], cwd=staging, logger=logger)
yarn_proc.wait()

dedupe_yarn(staging, logger)

if not osp.exists(pjoin(HERE, 'static')):
# Determine whether to build.
target = pjoin(HERE, 'static', 'index.html')
if not osp.exists(target):
ensure_node_modules(staging, logger)
yarn_proc = Process(['node', YARN_PATH, 'build'], cwd=staging,
logger=logger)
yarn_proc.wait()


def ensure_app(app_dir):
"""Ensure that an application directory is available.
If it does not exist, return a list of messages to prompt the user.
"""
if osp.exists(pjoin(app_dir, 'static', 'index.html')):
return

msgs = ['JupyterLab application assets not found in "%s"' % app_dir,
'Please run `jupyter lab build` or use a different app directory']
return msgs


def watch_packages(logger=None):
"""Run watch mode for the source packages.
Expand All @@ -159,14 +182,9 @@ def watch_packages(logger=None):
A list of `WatchHelper` objects.
"""
parent = pjoin(HERE, '..')

if not osp.exists(pjoin(parent, 'node_modules')):
yarn_proc = Process(['node', YARN_PATH], cwd=parent, logger=logger)
yarn_proc.wait()

dedupe_yarn(parent, logger)

logger = _ensure_logger(logger)
ensure_node_modules(parent, logger)

ts_dir = osp.abspath(osp.join(HERE, '..', 'packages', 'metapackage'))

# Run typescript watch and wait for the string indicating it is done.
Expand Down
51 changes: 33 additions & 18 deletions jupyterlab/extension.py
Expand Up @@ -82,16 +82,19 @@ def load_jupyter_server_extension(nbapp):
from notebook._version import version_info
from tornado.ioloop import IOLoop
from markupsafe import Markup
from .build_handler import build_path, Builder, BuildHandler
from .extension_manager_handler import (
from .handlers.build_handler import build_path, Builder, BuildHandler
from .handlers.extension_manager_handler import (
extensions_handler_path, ExtensionManager, ExtensionHandler
)
from .handlers.error_handler import ErrorHandler
from .commands import (
DEV_DIR, HERE, ensure_core, ensure_dev, watch, watch_dev, get_app_dir
DEV_DIR, HERE, ensure_app, ensure_core, ensure_dev, watch,
watch_dev, get_app_dir
)

web_app = nbapp.web_app
logger = nbapp.log
base_url = nbapp.base_url

# Handle the app_dir
app_dir = getattr(nbapp, 'app_dir', get_app_dir())
Expand Down Expand Up @@ -133,14 +136,14 @@ def load_jupyter_server_extension(nbapp):
page_config = web_app.settings.setdefault('page_config_data', dict())
page_config['buildAvailable'] = not core_mode and not dev_mode
page_config['buildCheck'] = not core_mode and not dev_mode
page_config['defaultWorkspace'] = ujoin(nbapp.base_url, config.page_url)
page_config['defaultWorkspace'] = ujoin(base_url, config.page_url)
page_config['devMode'] = dev_mode
page_config['token'] = nbapp.token

# Handle bundle url
bundle_url = config.public_url
if bundle_url.startswith(config.page_url):
bundle_url = ujoin(nbapp.base_url, bundle_url)
bundle_url = ujoin(base_url, bundle_url)
page_config['bundleUrl'] = bundle_url

# Export the version info tuple to a JSON array. This gets printed
Expand All @@ -157,18 +160,34 @@ def load_jupyter_server_extension(nbapp):
nbapp.default_url = uri
nbapp.file_to_run = ''

# Print messages.
logger.info('JupyterLab extension loaded from %s' % HERE)
logger.info('JupyterLab application directory is %s' % app_dir)

build_url = ujoin(base_url, build_path)
builder = Builder(logger, core_mode, app_dir)
build_handler = (build_url, BuildHandler, {'builder': builder})
handlers = [build_handler]

errored = False

if core_mode:
logger.info(CORE_NOTE.strip())
ensure_core(logger)

elif dev_mode:
ensure_dev(logger)
if not watch_mode:
ensure_dev(logger)
logger.info(DEV_NOTE)

# Print messages.
logger.info('JupyterLab extension loaded from %s' % HERE)
logger.info('JupyterLab application directory is %s' % app_dir)
# Make sure the app dir exists.
else:
msgs = ensure_app(app_dir)
if msgs:
[logger.error(msg) for msg in msgs]
handler = (ujoin(base_url, '/lab'), ErrorHandler, { 'messages': msgs })
handlers.append(handler)
errored = True

if watch_mode:
logger.info('Starting JupyterLab watch mode...')
Expand All @@ -183,19 +202,15 @@ def load_jupyter_server_extension(nbapp):

config.cache_files = False

base_url = web_app.settings['base_url']
build_url = ujoin(base_url, build_path)
builder = Builder(logger, core_mode, app_dir)
build_handler = (build_url, BuildHandler, {'builder': builder})
handlers = [build_handler]

if not core_mode:
if not core_mode and not errored:
ext_url = ujoin(base_url, extensions_handler_path)
ext_manager = ExtensionManager(logger, app_dir)
ext_handler = (ext_url, ExtensionHandler, {'manager': ext_manager})
handlers.append(ext_handler)

# Must add before the launcher handlers to avoid shadowing.
# Must add before the root server handlers to avoid shadowing.
web_app.add_handlers('.*$', handlers)

add_handlers(web_app, config)
# Add the root handlers if we have not errored.
if not errored:
add_handlers(web_app, config)
Empty file added jupyterlab/handlers/__init__.py
Empty file.
Expand Up @@ -10,7 +10,7 @@
from tornado import gen, web
from tornado.concurrent import run_on_executor

from .commands import build, clean, build_check
from ..commands import build, clean, build_check


class Builder(object):
Expand Down
33 changes: 33 additions & 0 deletions jupyterlab/handlers/error_handler.py
@@ -0,0 +1,33 @@
# coding: utf-8
"""An error handler for JupyterLab."""

# Copyright (c) Jupyter Development Team.
# Distributed under the terms of the Modified BSD License.

from tornado import web
from jupyterlab_server.server import JupyterHandler


TEMPLATE = """
<!DOCTYPE HTML>
<html>
<head>
<meta charset="utf-8">
<title>JupyterLab Error</title>
</head>
<body>
<h1>JupyterLab Error<h1>
%s
</body>
"""

class ErrorHandler(JupyterHandler):

def initialize(self, messages):
self.messages = messages

@web.authenticated
@web.removeslash
def get(self):
msgs = ['<h2>%s</h2>' % msg for msg in self.messages]
self.write(TEMPLATE % '\n'.join(msgs))
Expand Up @@ -12,7 +12,7 @@
from notebook.base.handlers import APIHandler
from tornado import gen, web

from .commands import (
from ..commands import (
get_app_info, install_extension, uninstall_extension,
enable_extension, disable_extension, read_package,
_AppHandler, get_latest_compatible_package_versions
Expand Down
16 changes: 9 additions & 7 deletions jupyterlab/labapp.py
Expand Up @@ -6,6 +6,8 @@

import json
import os
import os.path as osp
from os.path import join as pjoin
import sys

from jupyter_core.application import JupyterApp, base_aliases
Expand Down Expand Up @@ -140,9 +142,9 @@ def start(self):
raw = (page_url if not self.extra_args
else ujoin(config.workspaces_url, self.extra_args[0]))
slug = slugify(raw, base_url)
workspace_path = os.path.join(directory, slug + WORKSPACE_EXTENSION)
workspace_path = pjoin(directory, slug + WORKSPACE_EXTENSION)

if os.path.exists(workspace_path):
if osp.exists(workspace_path):
with open(workspace_path) as fid:
try: # to load the workspace file.
print(fid.read())
Expand Down Expand Up @@ -195,15 +197,15 @@ def start(self):
print('%s is not a valid workspace:\n%s' % (fid.name, e))
sys.exit(1)

if not os.path.exists(directory):
if not osp.exists(directory):
try:
os.makedirs(directory)
except Exception as e:
print('Workspaces directory could not be created:\n%s' % e)
sys.exit(1)

slug = slugify(workspace['metadata']['id'], base_url)
workspace_path = os.path.join(directory, slug + WORKSPACE_EXTENSION)
workspace_path = pjoin(directory, slug + WORKSPACE_EXTENSION)

# Write the workspace data to a file.
with open(workspace_path, 'w') as fid:
Expand All @@ -217,12 +219,12 @@ def _smart_open(self):
if file_name == '-':
return sys.stdin
else:
file_path = os.path.abspath(file_name)
file_path = osp.abspath(file_name)

if not os.path.exists(file_path):
if not osp.exists(file_path):
print('%s does not exist.' % file_name)
sys.exit(1)

return open(file_path)

def _validate(self, data, base_url, page_url, workspaces_url):
Expand Down

0 comments on commit fd4e23b

Please sign in to comment.