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.
		
		
		
		
		
			
		
			
				
	
	
		
			163 lines
		
	
	
		
			5.0 KiB
		
	
	
	
		
			Python
		
	
			
		
		
	
	
			163 lines
		
	
	
		
			5.0 KiB
		
	
	
	
		
			Python
		
	
"""Tornado handlers for frontend config storage."""
 | 
						|
 | 
						|
# Copyright (c) Jupyter Development Team.
 | 
						|
# Distributed under the terms of the Modified BSD License.
 | 
						|
import json
 | 
						|
from concurrent.futures import ThreadPoolExecutor
 | 
						|
from threading import Event
 | 
						|
 | 
						|
from jupyter_server.base.handlers import APIHandler
 | 
						|
from jupyter_server.extension.handler import ExtensionHandlerMixin
 | 
						|
from tornado import gen, web
 | 
						|
from tornado.concurrent import run_on_executor
 | 
						|
 | 
						|
from jupyterlab.commands import AppOptions, _ensure_options, build, build_check, clean
 | 
						|
 | 
						|
 | 
						|
class Builder:
 | 
						|
    building = False
 | 
						|
    executor = ThreadPoolExecutor(max_workers=5)
 | 
						|
    canceled = False
 | 
						|
    _canceling = False
 | 
						|
    _kill_event = None
 | 
						|
    _future = None
 | 
						|
 | 
						|
    def __init__(self, core_mode, app_options=None):
 | 
						|
        app_options = _ensure_options(app_options)
 | 
						|
        self.log = app_options.logger
 | 
						|
        self.core_mode = core_mode
 | 
						|
        self.app_dir = app_options.app_dir
 | 
						|
        self.core_config = app_options.core_config
 | 
						|
        self.labextensions_path = app_options.labextensions_path
 | 
						|
 | 
						|
    @gen.coroutine
 | 
						|
    def get_status(self):
 | 
						|
        if self.core_mode:
 | 
						|
            raise gen.Return({"status": "stable", "message": ""})
 | 
						|
        if self.building:
 | 
						|
            raise gen.Return({"status": "building", "message": ""})
 | 
						|
 | 
						|
        try:
 | 
						|
            messages = yield self._run_build_check(
 | 
						|
                self.app_dir, self.log, self.core_config, self.labextensions_path
 | 
						|
            )
 | 
						|
            status = "needed" if messages else "stable"
 | 
						|
            if messages:
 | 
						|
                self.log.warning("Build recommended")
 | 
						|
                [self.log.warning(m) for m in messages]
 | 
						|
            else:
 | 
						|
                self.log.info("Build is up to date")
 | 
						|
        except ValueError:
 | 
						|
            self.log.warning("Could not determine jupyterlab build status without nodejs")
 | 
						|
            status = "stable"
 | 
						|
            messages = []
 | 
						|
 | 
						|
        raise gen.Return({"status": status, "message": "\n".join(messages)})
 | 
						|
 | 
						|
    @gen.coroutine
 | 
						|
    def build(self):
 | 
						|
        if self._canceling:
 | 
						|
            msg = "Cancel in progress"
 | 
						|
            raise ValueError(msg)
 | 
						|
        if not self.building:
 | 
						|
            self.canceled = False
 | 
						|
            self._future = future = gen.Future()
 | 
						|
            self.building = True
 | 
						|
            self._kill_event = evt = Event()
 | 
						|
            try:
 | 
						|
                yield self._run_build(
 | 
						|
                    self.app_dir, self.log, evt, self.core_config, self.labextensions_path
 | 
						|
                )
 | 
						|
                future.set_result(True)
 | 
						|
            except Exception as e:
 | 
						|
                if str(e) == "Aborted":
 | 
						|
                    future.set_result(False)
 | 
						|
                else:
 | 
						|
                    future.set_exception(e)
 | 
						|
            finally:
 | 
						|
                self.building = False
 | 
						|
        try:
 | 
						|
            yield self._future
 | 
						|
        except Exception as e:
 | 
						|
            raise e
 | 
						|
 | 
						|
    @gen.coroutine
 | 
						|
    def cancel(self):
 | 
						|
        if not self.building:
 | 
						|
            msg = "No current build"
 | 
						|
            raise ValueError(msg)
 | 
						|
        self._canceling = True
 | 
						|
        yield self._future
 | 
						|
        self._canceling = False
 | 
						|
        self.canceled = True
 | 
						|
 | 
						|
    @run_on_executor
 | 
						|
    def _run_build_check(self, app_dir, logger, core_config, labextensions_path):
 | 
						|
        return build_check(
 | 
						|
            app_options=AppOptions(
 | 
						|
                app_dir=app_dir,
 | 
						|
                logger=logger,
 | 
						|
                core_config=core_config,
 | 
						|
                labextensions_path=labextensions_path,
 | 
						|
            )
 | 
						|
        )
 | 
						|
 | 
						|
    @run_on_executor
 | 
						|
    def _run_build(self, app_dir, logger, kill_event, core_config, labextensions_path):
 | 
						|
        app_options = AppOptions(
 | 
						|
            app_dir=app_dir,
 | 
						|
            logger=logger,
 | 
						|
            kill_event=kill_event,
 | 
						|
            core_config=core_config,
 | 
						|
            labextensions_path=labextensions_path,
 | 
						|
        )
 | 
						|
        try:
 | 
						|
            return build(app_options=app_options)
 | 
						|
        except Exception:
 | 
						|
            if self._kill_event.is_set():
 | 
						|
                return
 | 
						|
            self.log.warning("Build failed, running a clean and rebuild")
 | 
						|
            clean(app_options=app_options)
 | 
						|
            return build(app_options=app_options)
 | 
						|
 | 
						|
 | 
						|
class BuildHandler(ExtensionHandlerMixin, APIHandler):
 | 
						|
    def initialize(self, builder=None, name=None):
 | 
						|
        super().initialize(name=name)
 | 
						|
        self.builder = builder
 | 
						|
 | 
						|
    @web.authenticated
 | 
						|
    @gen.coroutine
 | 
						|
    def get(self):
 | 
						|
        data = yield self.builder.get_status()
 | 
						|
        self.finish(json.dumps(data))
 | 
						|
 | 
						|
    @web.authenticated
 | 
						|
    @gen.coroutine
 | 
						|
    def delete(self):
 | 
						|
        self.log.warning("Canceling build")
 | 
						|
        try:
 | 
						|
            yield self.builder.cancel()
 | 
						|
        except Exception as e:
 | 
						|
            raise web.HTTPError(500, str(e)) from None
 | 
						|
        self.set_status(204)
 | 
						|
 | 
						|
    @web.authenticated
 | 
						|
    @gen.coroutine
 | 
						|
    def post(self):
 | 
						|
        self.log.debug("Starting build")
 | 
						|
        try:
 | 
						|
            yield self.builder.build()
 | 
						|
        except Exception as e:
 | 
						|
            raise web.HTTPError(500, str(e)) from None
 | 
						|
 | 
						|
        if self.builder.canceled:
 | 
						|
            raise web.HTTPError(400, "Build canceled")
 | 
						|
 | 
						|
        self.log.debug("Build succeeded")
 | 
						|
        self.set_status(200)
 | 
						|
 | 
						|
 | 
						|
# The path for lab build.
 | 
						|
build_path = r"/lab/api/build"
 |