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