File size: 6,296 Bytes
d1ceb73 |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 |
# 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
|