Skip to content

Commit 6fa038f

Browse files
authored
Move workspace management in jupyterlab_server (#227)
1 parent 7b9c024 commit 6fa038f

File tree

4 files changed

+316
-112
lines changed

4 files changed

+316
-112
lines changed

jupyterlab_server/__init__.py

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,8 @@
44
from .app import LabServerApp
55
from .licenses_app import LicensesApp
66
from .handlers import add_handlers, LabHandler, LabConfig
7-
from .workspaces_handler import slugify, WORKSPACE_EXTENSION
7+
from .translation_utils import translator
8+
from .workspaces_app import WorkspaceExportApp, WorkspaceImportApp, WorkspaceListApp
89
from ._version import __version__
910

1011
__all__ = [
@@ -13,9 +14,12 @@
1314
'LabConfig',
1415
'LabHandler',
1516
'LabServerApp',
16-
'slugify',
17+
'LicensesApp',
1718
'SETTINGS_EXTENSION',
18-
'WORKSPACE_EXTENSION'
19+
'translator',
20+
'WorkspaceExportApp',
21+
'WorkspaceImportApp',
22+
'WorkspaceListApp'
1923
]
2024

2125
def _jupyter_server_extension_points():

jupyterlab_server/handlers.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@
1818
from .settings_handler import SettingsHandler
1919
from .themes_handler import ThemesHandler
2020
from .translations_handler import TranslationsHandler
21-
from .workspaces_handler import WorkspacesHandler
21+
from .workspaces_handler import WorkspacesHandler, WorkspacesManager
2222
from .licenses_handler import LicensesHandler, LicensesManager
2323

2424
# -----------------------------------------------------------------------------
@@ -239,7 +239,7 @@ def add_handlers(handlers, extension_app):
239239
if extension_app.workspaces_dir:
240240

241241
workspaces_config = {
242-
'path': extension_app.workspaces_dir
242+
'manager': WorkspacesManager(extension_app.workspaces_dir)
243243
}
244244

245245
# Handle requests for the list of workspaces. Make slash optional.
Lines changed: 171 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,171 @@
1+
""""""
2+
import json
3+
import sys
4+
from pathlib import Path
5+
6+
from jupyter_core.application import JupyterApp
7+
from traitlets import Bool, Unicode
8+
9+
from ._version import __version__
10+
from .config import LabConfig
11+
from .workspaces_handler import WorkspacesManager
12+
13+
14+
class WorkspaceListApp(JupyterApp, LabConfig):
15+
version = __version__
16+
description = """
17+
Print all the workspaces available
18+
19+
If '--json' flag is passed in, a single 'json' object is printed.
20+
If '--jsonlines' flag is passed in, 'json' object of each workspace separated by a new line is printed.
21+
If nothing is passed in, workspace ids list is printed.
22+
"""
23+
flags = dict(
24+
jsonlines=(
25+
{"WorkspaceListApp": {"jsonlines": True}},
26+
("Produce machine-readable JSON Lines output."),
27+
),
28+
json=(
29+
{"WorkspaceListApp": {"json": True}},
30+
("Produce machine-readable JSON object output."),
31+
),
32+
)
33+
34+
jsonlines = Bool(
35+
False,
36+
config=True,
37+
help=(
38+
"If True, the output will be a newline-delimited JSON (see https://jsonlines.org/) of objects, "
39+
"one per JupyterLab workspace, each with the details of the relevant workspace"
40+
),
41+
)
42+
json = Bool(
43+
False,
44+
config=True,
45+
help=(
46+
"If True, each line of output will be a JSON object with the "
47+
"details of the workspace."
48+
),
49+
)
50+
51+
def initialize(self, *args, **kwargs):
52+
super().initialize(*args, **kwargs)
53+
self.manager = WorkspacesManager(self.workspaces_dir)
54+
55+
def start(self):
56+
workspaces = self.manager.list_workspaces()
57+
if self.jsonlines:
58+
for workspace in workspaces:
59+
print(json.dumps(workspace))
60+
elif self.json:
61+
print(json.dumps(workspaces))
62+
else:
63+
for workspace in workspaces:
64+
print(workspace["metadata"]["id"])
65+
66+
67+
class WorkspaceExportApp(JupyterApp, LabConfig):
68+
version = __version__
69+
description = """
70+
Export a JupyterLab workspace
71+
72+
If no arguments are passed in, this command will export the default
73+
workspace.
74+
If a workspace name is passed in, this command will export that workspace.
75+
If no workspace is found, this command will export an empty workspace.
76+
"""
77+
78+
def initialize(self, *args, **kwargs):
79+
super().initialize(*args, **kwargs)
80+
self.manager = WorkspacesManager(self.workspaces_dir)
81+
82+
def start(self):
83+
if len(self.extra_args) > 1:
84+
print("Too many arguments were provided for workspace export.")
85+
self.exit(1)
86+
87+
raw = self.extra_args[0]
88+
try:
89+
workspace = self.manager.load(raw)
90+
print(json.dumps(workspace))
91+
except Exception as e:
92+
print(json.dumps(dict(data=dict(), metadata=dict(id=raw))))
93+
94+
95+
class WorkspaceImportApp(JupyterApp, LabConfig):
96+
version = __version__
97+
description = """
98+
Import a JupyterLab workspace
99+
100+
This command will import a workspace from a JSON file. The format of the
101+
file must be the same as what the export functionality emits.
102+
"""
103+
workspace_name = Unicode(
104+
None,
105+
config=True,
106+
allow_none=True,
107+
help="""
108+
Workspace name. If given, the workspace ID in the imported
109+
file will be replaced with a new ID pointing to this
110+
workspace name.
111+
""",
112+
)
113+
114+
aliases = {"name": "WorkspaceImportApp.workspace_name"}
115+
116+
def initialize(self, *args, **kwargs):
117+
super().initialize(*args, **kwargs)
118+
self.manager = WorkspacesManager(self.workspaces_dir)
119+
120+
def start(self):
121+
122+
if len(self.extra_args) != 1:
123+
print("One argument is required for workspace import.")
124+
self.exit(1)
125+
126+
with self._smart_open() as fid:
127+
try: # to load, parse, and validate the workspace file.
128+
workspace = self._validate(fid)
129+
except Exception as e:
130+
print("%s is not a valid workspace:\n%s" % (fid.name, e))
131+
self.exit(1)
132+
133+
try:
134+
workspace_path = self.manager.save(
135+
workspace["metadata"]["id"], json.dumps(workspace)
136+
)
137+
except Exception as e:
138+
print(f"Workspace could not be exported:\n{e!s}")
139+
self.exit(1)
140+
141+
print(f"Saved workspace: {workspace_path!s}")
142+
143+
def _smart_open(self):
144+
file_name = self.extra_args[0]
145+
146+
if file_name == "-":
147+
return sys.stdin
148+
else:
149+
file_path = Path(file_name).resolve()
150+
151+
if not file_path.exists():
152+
print(f"{file_name!s} does not exist.")
153+
self.exit(1)
154+
155+
return file_path.open(encoding="utf-8")
156+
157+
def _validate(self, data):
158+
workspace = json.load(data)
159+
160+
if "data" not in workspace:
161+
raise Exception("The `data` field is missing.")
162+
163+
# If workspace_name is set in config, inject the
164+
# name into the workspace metadata.
165+
if self.workspace_name is not None and self.workspace_name != "":
166+
workspace["metadata"] = {"id": self.workspace_name}
167+
else:
168+
if "id" not in workspace["metadata"]:
169+
raise Exception("The `id` field is missing in `metadata`.")
170+
171+
return workspace

0 commit comments

Comments
 (0)