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.
		
		
		
		
		
			
		
			
				
	
	
		
			193 lines
		
	
	
		
			6.1 KiB
		
	
	
	
		
			Python
		
	
			
		
		
	
	
			193 lines
		
	
	
		
			6.1 KiB
		
	
	
	
		
			Python
		
	
# Copyright (c) Jupyter Development Team.
 | 
						|
# Distributed under the terms of the Modified BSD License.
 | 
						|
 | 
						|
"""A workspace management CLI"""
 | 
						|
from __future__ import annotations
 | 
						|
 | 
						|
import json
 | 
						|
import sys
 | 
						|
import warnings
 | 
						|
from pathlib import Path
 | 
						|
from typing import Any
 | 
						|
 | 
						|
from jupyter_core.application import JupyterApp
 | 
						|
from traitlets import Bool, Unicode
 | 
						|
 | 
						|
from ._version import __version__
 | 
						|
from .config import LabConfig
 | 
						|
from .workspaces_handler import WorkspacesManager
 | 
						|
 | 
						|
# Default workspace ID
 | 
						|
#  Needs to match PageConfig.defaultWorkspace define in packages/coreutils/src/pageconfig.ts
 | 
						|
DEFAULT_WORKSPACE = "default"
 | 
						|
 | 
						|
 | 
						|
class WorkspaceListApp(JupyterApp, LabConfig):
 | 
						|
    """An app to list workspaces."""
 | 
						|
 | 
						|
    version = __version__
 | 
						|
    description = """
 | 
						|
    Print all the workspaces available
 | 
						|
 | 
						|
    If '--json' flag is passed in, a single 'json' object is printed.
 | 
						|
    If '--jsonlines' flag is passed in, 'json' object of each workspace separated by a new line is printed.
 | 
						|
    If nothing is passed in, workspace ids list is printed.
 | 
						|
    """
 | 
						|
    flags = dict(
 | 
						|
        jsonlines=(
 | 
						|
            {"WorkspaceListApp": {"jsonlines": True}},
 | 
						|
            ("Produce machine-readable JSON Lines output."),
 | 
						|
        ),
 | 
						|
        json=(
 | 
						|
            {"WorkspaceListApp": {"json": True}},
 | 
						|
            ("Produce machine-readable JSON object output."),
 | 
						|
        ),
 | 
						|
    )
 | 
						|
 | 
						|
    jsonlines = Bool(
 | 
						|
        False,
 | 
						|
        config=True,
 | 
						|
        help=(
 | 
						|
            "If True, the output will be a newline-delimited JSON (see https://jsonlines.org/) of objects, "
 | 
						|
            "one per JupyterLab workspace, each with the details of the relevant workspace"
 | 
						|
        ),
 | 
						|
    )
 | 
						|
    json = Bool(
 | 
						|
        False,
 | 
						|
        config=True,
 | 
						|
        help=(
 | 
						|
            "If True, each line of output will be a JSON object with the "
 | 
						|
            "details of the workspace."
 | 
						|
        ),
 | 
						|
    )
 | 
						|
 | 
						|
    def initialize(self, *args: Any, **kwargs: Any) -> None:
 | 
						|
        """Initialize the app."""
 | 
						|
        super().initialize(*args, **kwargs)
 | 
						|
        self.manager = WorkspacesManager(self.workspaces_dir)
 | 
						|
 | 
						|
    def start(self) -> None:
 | 
						|
        """Start the app."""
 | 
						|
        workspaces = self.manager.list_workspaces()
 | 
						|
        if self.jsonlines:
 | 
						|
            for workspace in workspaces:
 | 
						|
                print(json.dumps(workspace))
 | 
						|
        elif self.json:
 | 
						|
            print(json.dumps(workspaces))
 | 
						|
        else:
 | 
						|
            for workspace in workspaces:
 | 
						|
                print(workspace["metadata"]["id"])
 | 
						|
 | 
						|
 | 
						|
class WorkspaceExportApp(JupyterApp, LabConfig):
 | 
						|
    """A workspace export app."""
 | 
						|
 | 
						|
    version = __version__
 | 
						|
    description = """
 | 
						|
    Export a JupyterLab workspace
 | 
						|
 | 
						|
    If no arguments are passed in, this command will export the default
 | 
						|
        workspace.
 | 
						|
    If a workspace name is passed in, this command will export that workspace.
 | 
						|
    If no workspace is found, this command will export an empty workspace.
 | 
						|
    """
 | 
						|
 | 
						|
    def initialize(self, *args: Any, **kwargs: Any) -> None:
 | 
						|
        """Initialize the app."""
 | 
						|
        super().initialize(*args, **kwargs)
 | 
						|
        self.manager = WorkspacesManager(self.workspaces_dir)
 | 
						|
 | 
						|
    def start(self) -> None:
 | 
						|
        """Start the app."""
 | 
						|
        if len(self.extra_args) > 1:  # pragma: no cover
 | 
						|
            warnings.warn("Too many arguments were provided for workspace export.")
 | 
						|
            self.exit(1)
 | 
						|
 | 
						|
        raw = DEFAULT_WORKSPACE if not self.extra_args else self.extra_args[0]
 | 
						|
        try:
 | 
						|
            workspace = self.manager.load(raw)
 | 
						|
            print(json.dumps(workspace))
 | 
						|
        except Exception:  # pragma: no cover
 | 
						|
            self.log.error(json.dumps(dict(data=dict(), metadata=dict(id=raw))))
 | 
						|
 | 
						|
 | 
						|
class WorkspaceImportApp(JupyterApp, LabConfig):
 | 
						|
    """A workspace import app."""
 | 
						|
 | 
						|
    version = __version__
 | 
						|
    description = """
 | 
						|
    Import a JupyterLab workspace
 | 
						|
 | 
						|
    This command will import a workspace from a JSON file. The format of the
 | 
						|
        file must be the same as what the export functionality emits.
 | 
						|
    """
 | 
						|
    workspace_name = Unicode(
 | 
						|
        None,
 | 
						|
        config=True,
 | 
						|
        allow_none=True,
 | 
						|
        help="""
 | 
						|
        Workspace name. If given, the workspace ID in the imported
 | 
						|
        file will be replaced with a new ID pointing to this
 | 
						|
        workspace name.
 | 
						|
        """,
 | 
						|
    )
 | 
						|
 | 
						|
    aliases = {"name": "WorkspaceImportApp.workspace_name"}
 | 
						|
 | 
						|
    def initialize(self, *args: Any, **kwargs: Any) -> None:
 | 
						|
        """Initialize the app."""
 | 
						|
        super().initialize(*args, **kwargs)
 | 
						|
        self.manager = WorkspacesManager(self.workspaces_dir)
 | 
						|
 | 
						|
    def start(self) -> None:
 | 
						|
        """Start the app."""
 | 
						|
        if len(self.extra_args) != 1:  # pragma: no cover
 | 
						|
            self.log.info("One argument is required for workspace import.")
 | 
						|
            self.exit(1)
 | 
						|
 | 
						|
        with self._smart_open() as fid:
 | 
						|
            try:  # to load, parse, and validate the workspace file.
 | 
						|
                workspace = self._validate(fid)
 | 
						|
            except Exception as e:  # pragma: no cover
 | 
						|
                self.log.info("%s is not a valid workspace:\n%s", fid.name, e)
 | 
						|
                self.exit(1)
 | 
						|
 | 
						|
        try:
 | 
						|
            workspace_path = self.manager.save(workspace["metadata"]["id"], json.dumps(workspace))
 | 
						|
        except Exception as e:  # pragma: no cover
 | 
						|
            self.log.info("Workspace could not be exported:\n%s", e)
 | 
						|
            self.exit(1)
 | 
						|
 | 
						|
        self.log.info("Saved workspace: %s", workspace_path)
 | 
						|
 | 
						|
    def _smart_open(self) -> Any:
 | 
						|
        file_name = self.extra_args[0]
 | 
						|
 | 
						|
        if file_name == "-":  # pragma: no cover
 | 
						|
            return sys.stdin
 | 
						|
 | 
						|
        file_path = Path(file_name).resolve()
 | 
						|
 | 
						|
        if not file_path.exists():  # pragma: no cover
 | 
						|
            self.log.info("%s does not exist.", file_name)
 | 
						|
            self.exit(1)
 | 
						|
 | 
						|
        return file_path.open(encoding="utf-8")
 | 
						|
 | 
						|
    def _validate(self, data: Any) -> Any:
 | 
						|
        workspace = json.load(data)
 | 
						|
 | 
						|
        if "data" not in workspace:
 | 
						|
            msg = "The `data` field is missing."
 | 
						|
            raise Exception(msg)
 | 
						|
 | 
						|
        # If workspace_name is set in config, inject the
 | 
						|
        # name into the workspace metadata.
 | 
						|
        if self.workspace_name is not None and self.workspace_name:
 | 
						|
            workspace["metadata"] = {"id": self.workspace_name}
 | 
						|
        elif "id" not in workspace["metadata"]:
 | 
						|
            msg = "The `id` field is missing in `metadata`."
 | 
						|
            raise Exception(msg)
 | 
						|
 | 
						|
        return workspace
 |