Skip to content

Commit

Permalink
Move workspace management in jupyterlab_server (#227)
Browse files Browse the repository at this point in the history
  • Loading branch information
fcollonval authored Dec 22, 2021
1 parent 7b9c024 commit 6fa038f
Show file tree
Hide file tree
Showing 4 changed files with 316 additions and 112 deletions.
10 changes: 7 additions & 3 deletions jupyterlab_server/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,8 @@
from .app import LabServerApp
from .licenses_app import LicensesApp
from .handlers import add_handlers, LabHandler, LabConfig
from .workspaces_handler import slugify, WORKSPACE_EXTENSION
from .translation_utils import translator
from .workspaces_app import WorkspaceExportApp, WorkspaceImportApp, WorkspaceListApp
from ._version import __version__

__all__ = [
Expand All @@ -13,9 +14,12 @@
'LabConfig',
'LabHandler',
'LabServerApp',
'slugify',
'LicensesApp',
'SETTINGS_EXTENSION',
'WORKSPACE_EXTENSION'
'translator',
'WorkspaceExportApp',
'WorkspaceImportApp',
'WorkspaceListApp'
]

def _jupyter_server_extension_points():
Expand Down
4 changes: 2 additions & 2 deletions jupyterlab_server/handlers.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
from .settings_handler import SettingsHandler
from .themes_handler import ThemesHandler
from .translations_handler import TranslationsHandler
from .workspaces_handler import WorkspacesHandler
from .workspaces_handler import WorkspacesHandler, WorkspacesManager
from .licenses_handler import LicensesHandler, LicensesManager

# -----------------------------------------------------------------------------
Expand Down Expand Up @@ -239,7 +239,7 @@ def add_handlers(handlers, extension_app):
if extension_app.workspaces_dir:

workspaces_config = {
'path': extension_app.workspaces_dir
'manager': WorkspacesManager(extension_app.workspaces_dir)
}

# Handle requests for the list of workspaces. Make slash optional.
Expand Down
171 changes: 171 additions & 0 deletions jupyterlab_server/workspaces_app.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,171 @@
""""""
import json
import sys
from pathlib import Path

from jupyter_core.application import JupyterApp
from traitlets import Bool, Unicode

from ._version import __version__
from .config import LabConfig
from .workspaces_handler import WorkspacesManager


class WorkspaceListApp(JupyterApp, LabConfig):
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, **kwargs):
super().initialize(*args, **kwargs)
self.manager = WorkspacesManager(self.workspaces_dir)

def start(self):
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):
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, **kwargs):
super().initialize(*args, **kwargs)
self.manager = WorkspacesManager(self.workspaces_dir)

def start(self):
if len(self.extra_args) > 1:
print("Too many arguments were provided for workspace export.")
self.exit(1)

raw = self.extra_args[0]
try:
workspace = self.manager.load(raw)
print(json.dumps(workspace))
except Exception as e:
print(json.dumps(dict(data=dict(), metadata=dict(id=raw))))


class WorkspaceImportApp(JupyterApp, LabConfig):
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, **kwargs):
super().initialize(*args, **kwargs)
self.manager = WorkspacesManager(self.workspaces_dir)

def start(self):

if len(self.extra_args) != 1:
print("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:
print("%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:
print(f"Workspace could not be exported:\n{e!s}")
self.exit(1)

print(f"Saved workspace: {workspace_path!s}")

def _smart_open(self):
file_name = self.extra_args[0]

if file_name == "-":
return sys.stdin
else:
file_path = Path(file_name).resolve()

if not file_path.exists():
print(f"{file_name!s} does not exist.")
self.exit(1)

return file_path.open(encoding="utf-8")

def _validate(self, data):
workspace = json.load(data)

if "data" not in workspace:
raise Exception("The `data` field is missing.")

# 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}
else:
if "id" not in workspace["metadata"]:
raise Exception("The `id` field is missing in `metadata`.")

return workspace
Loading

0 comments on commit 6fa038f

Please sign in to comment.