Skip to content

Commit

Permalink
Create lab plugin, common to jupyterlab and retrolab
Browse files Browse the repository at this point in the history
  • Loading branch information
davidbrochart committed Sep 24, 2021
1 parent bffd6c7 commit de959d2
Show file tree
Hide file tree
Showing 15 changed files with 309 additions and 462 deletions.
7 changes: 4 additions & 3 deletions .github/workflows/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -36,12 +36,12 @@ jobs:

- name: Install jupyverse
run: |
pip install git+https://github.com/jupyter-server/fps
pip install fps-uvicorn==0.0.2
mkdir fps && cd fps && curl -L -O https://github.com/jupyter-server/fps/archive/master.tar.gz && tar zxf master.tar.gz && cd fps-master && pip install . && pip install ./plugins/uvicorn && cd ../.. && rm -rf fps
pip install . --no-deps
pip install ./plugins/contents
pip install ./plugins/auth
pip install ./plugins/kernels
pip install ./plugins/lab
pip install ./plugins/jupyterlab
pip install flake8 black mypy pytest requests
Expand All @@ -53,8 +53,9 @@ jobs:
mypy plugins/contents
mypy plugins/kernels
mypy plugins/retrolab
mypy plugins/auth
mypy plugins/jupyterlab
mypy plugins/lab
mypy plugins/auth
mypy plugins/nbconvert
mypy plugins/yjs
mypy plugins/terminals
Expand Down
1 change: 1 addition & 0 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
rm -rf dist; python setup.py sdist bdist_wheel
cd plugins/auth ; rm -rf dist && python setup.py sdist bdist_wheel ; cp dist/* ../../dist/ ; cd ../..
cd plugins/contents ; rm -rf dist && python setup.py sdist bdist_wheel ; cp dist/* ../../dist/ ; cd ../..
cd plugins/lab ; rm -rf dist && python setup.py sdist bdist_wheel ; cp dist/* ../../dist/ ; cd ../..
cd plugins/jupyterlab ; rm -rf dist && python setup.py sdist bdist_wheel ; cp dist/* ../../dist/ ; cd ../..
cd plugins/retrolab ; rm -rf dist && python setup.py sdist bdist_wheel ; cp dist/* ../../dist/ ; cd ../..
cd plugins/kernels ; rm -rf dist && python setup.py sdist bdist_wheel ; cp dist/* ../../dist/ ; cd ../..
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,7 @@ You can currently authenticate as an anonymous user, or
## With collaborative editing

```bash
jupyverse --open-browser --JupyterLab.collaborative # or --RetroLab.collaborative
jupyverse --open-browser --Lab.collaborative
```

This is especially interesting if you are "user-authenticated", since your will appear as the
Expand Down
2 changes: 1 addition & 1 deletion fps.toml
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
[fps]
open_browser = true

[JupyterLab]
[Lab]
collaborative = true
3 changes: 1 addition & 2 deletions plugins/jupyterlab/fps_jupyterlab/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,7 @@


class JupyterLabConfig(PluginModel):
collaborative: bool = False
base_url: str = "/"
pass


def get_jlab_config():
Expand Down
245 changes: 12 additions & 233 deletions plugins/jupyterlab/fps_jupyterlab/routes.py
Original file line number Diff line number Diff line change
@@ -1,131 +1,46 @@
import json
from pathlib import Path
import sys
from glob import glob
from http import HTTPStatus
import pkg_resources # type: ignore
from typing import Optional

from babel import Locale # type: ignore
import jupyterlab # type: ignore
import jupyverse # type: ignore
from fastapi import APIRouter, Response, Depends, status
from fastapi.responses import HTMLResponse, FileResponse, RedirectResponse
from fastapi import APIRouter, Response, Depends
from fastapi.responses import HTMLResponse
from fastapi.staticfiles import StaticFiles
from starlette.requests import Request # type: ignore
from fps.hooks import register_router # type: ignore

from fps_auth.db import get_user_db # type: ignore
from fps_auth.backends import ( # type: ignore
current_user,
cookie_authentication,
LoginCookieAuthentication,
get_user_manager,
)
from fps_auth.backends import current_user # type: ignore
from fps_auth.models import User # type: ignore
from fps_auth.config import get_auth_config # type: ignore
from fps_lab.routes import init_router # type: ignore

from .config import get_jlab_config
from fps_lab.config import get_lab_config # type: ignore

router = APIRouter()
prefix_dir: Path = Path(sys.prefix)
LOCALE = "en"
prefix_dir, federated_extensions = init_router(router, "lab")

router.mount(
"/static/lab",
StaticFiles(directory=prefix_dir / "share" / "jupyter" / "lab" / "static"),
name="static",
)

router.mount(
"/lab/api/themes",
StaticFiles(directory=prefix_dir / "share" / "jupyter" / "lab" / "themes"),
name="themes",
)

federated_extensions = []
for path in glob(
str(prefix_dir / "share" / "jupyter" / "labextensions" / "**" / "package.json"),
recursive=True,
):
with open(path) as f:
package = json.load(f)
name = package["name"]
extension = package["jupyterlab"]["_build"]
extension["name"] = name
federated_extensions.append(extension)
router.mount(
f"/lab/extensions/{name}/static",
StaticFiles(
directory=prefix_dir
/ "share"
/ "jupyter"
/ "labextensions"
/ name
/ "static"
),
name=name,
)


@router.get("/")
async def get_root(
response: Response,
token: Optional[str] = None,
auth_config=Depends(get_auth_config),
jlab_config=Depends(get_jlab_config),
user_db=Depends(get_user_db),
user_manager=Depends(get_user_manager),
):
if token and auth_config.mode == "token":
user = await user_db.get(token)
if user:
await super(
LoginCookieAuthentication, cookie_authentication
).get_login_response(user, response, user_manager)
# auto redirect
response.status_code = status.HTTP_302_FOUND
response.headers["Location"] = jlab_config.base_url + "lab"


@router.get("/lab")
async def get_lab(
user: User = Depends(current_user()), jlab_config=Depends(get_jlab_config)
user: User = Depends(current_user()), lab_config=Depends(get_lab_config)
):
return HTMLResponse(
get_index("default", jlab_config.collaborative, jlab_config.base_url)
get_index("default", lab_config.collaborative, lab_config.base_url)
)


@router.get("/lab/tree/{path:path}")
async def load_workspace(path, jlab_config=Depends(get_jlab_config)):
async def load_workspace(path, lab_config=Depends(get_lab_config)):
return HTMLResponse(
get_index("default", jlab_config.collaborative, jlab_config.base_url)
get_index("default", lab_config.collaborative, lab_config.base_url)
)


@router.get("/favicon.ico")
async def get_favicon():
return FileResponse(Path(jupyverse.__file__).parent / "static" / "favicon.ico")


@router.get("/static/notebook/components/MathJax/{rest_of_path:path}")
async def get_mathjax(rest_of_path):
return RedirectResponse(
"https://cdnjs.cloudflare.com/ajax/libs/mathjax/2.7.7/" + rest_of_path
)


@router.get("/lab/api/listings/@jupyterlab/extensionmanager-extension/listings.json")
async def get_listings():
return {
"blocked_extensions_uris": [],
"allowed_extensions_uris": [],
"blocked_extensions": [],
"allowed_extensions": [],
}


@router.get("/lab/api/workspaces/{name}")
async def get_workspace_data(user: User = Depends(current_user())):
if user:
Expand All @@ -149,145 +64,9 @@ async def set_workspace(

@router.get("/lab/workspaces/{name}", response_class=HTMLResponse)
async def get_workspace(
name, user: User = Depends(current_user()), jlab_config=Depends(get_jlab_config)
name, user: User = Depends(current_user()), lab_config=Depends(get_lab_config)
):
return get_index(name, jlab_config.collaborative, jlab_config.base_url)


@router.get("/lab/api/translations")
async def get_translations():
locale = Locale.parse("en")
data = {
"en": {
"displayName": locale.get_display_name(LOCALE).capitalize(),
"nativeName": locale.get_display_name().capitalize(),
}
}
for ep in pkg_resources.iter_entry_points(group="jupyterlab.languagepack"):
locale = Locale.parse(ep.name)
data[ep.name] = {
"displayName": locale.get_display_name(LOCALE).capitalize(),
"nativeName": locale.get_display_name().capitalize(),
}
return {"data": data, "message": ""}


@router.get("/lab/api/translations/{language}")
async def get_translation(
language,
):
global LOCALE
if language == "en":
LOCALE = language
return {}
for ep in pkg_resources.iter_entry_points(group="jupyterlab.languagepack"):
if ep.name == language:
break
else:
return {"data": {}, "message": f"Language pack '{language}' not installed!"}
LOCALE = language
package = ep.load()
data = {}
for path in (
Path(package.__file__).parent / "locale" / "fr_FR" / "LC_MESSAGES"
).glob("*.json"):
with open(path) as f:
data.update({path.stem: json.load(f)})
return {"data": data, "message": ""}


@router.get("/lab/api/settings/{name0}/{name1}:{name2}")
async def get_setting(
name0,
name1,
name2,
user: User = Depends(current_user()),
):
with open(
prefix_dir / "share" / "jupyter" / "lab" / "static" / "package.json"
) as f:
package = json.load(f)
if name0 == "@jupyterlab":
lab_or_extensions = Path("lab")
else:
lab_or_extensions = Path("labextensions") / name0 / name1
with open(
prefix_dir
/ "share"
/ "jupyter"
/ lab_or_extensions
/ "schemas"
/ name0
/ name1
/ f"{name2}.json"
) as f:
schema = json.load(f)
key = f"{name1}:{name2}"
result = {
"id": f"@jupyterlab/{key}",
"schema": schema,
"version": package["version"],
"raw": "{}",
"settings": {},
"last_modified": None,
"created": None,
}
if user:
settings = json.loads(user.settings)
if key in settings:
result.update(settings[key])
return result


@router.put(
"/lab/api/settings/@jupyterlab/{name0}:{name1}",
status_code=204,
)
async def change_setting(
request: Request,
name0,
name1,
user: User = Depends(current_user()),
user_db=Depends(get_user_db),
):
settings = json.loads(user.settings)
settings[f"{name0}:{name1}"] = await request.json()
user.settings = json.dumps(settings)
await user_db.update(user)
return Response(status_code=HTTPStatus.NO_CONTENT.value)


@router.get("/lab/api/settings")
async def get_settings(user: User = Depends(current_user())):
with open(
prefix_dir / "share" / "jupyter" / "lab" / "static" / "package.json"
) as f:
package = json.load(f)
if user:
user_settings = json.loads(user.settings)
else:
user_settings = {}
settings = []
for path in (
prefix_dir / "share" / "jupyter" / "lab" / "schemas" / "@jupyterlab"
).glob("*/*.json"):
with open(path) as f:
schema = json.load(f)
key = f"{path.parent.name}:{path.stem}"
setting = {
"id": f"@jupyterlab/{key}",
"schema": schema,
"version": package["version"],
"raw": "{}",
"settings": {},
"warning": None,
"last_modified": None,
"created": None,
}
if key in user_settings:
setting.update(user_settings[key])
settings.append(setting)
return {"settings": settings}
return get_index(name, lab_config.collaborative, lab_config.base_url)


INDEX_HTML = """\
Expand Down
3 changes: 1 addition & 2 deletions plugins/jupyterlab/setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,8 @@
install_requires=[
"fps",
"jupyterlab",
"aiofiles",
"fps-lab",
"fps-auth",
"babel",
],
entry_points={
"fps_router": ["fps-jupyterlab = fps_jupyterlab.routes"],
Expand Down
Empty file added plugins/lab/fps_lab/__init__.py
Empty file.
15 changes: 15 additions & 0 deletions plugins/lab/fps_lab/config.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
from fps.config import PluginModel, get_config # type: ignore
from fps.hooks import register_config, register_plugin_name # type: ignore


class LabConfig(PluginModel):
collaborative: bool = False
base_url: str = "/"


def get_lab_config():
return get_config(LabConfig)


c = register_config(LabConfig)
n = register_plugin_name("Lab")
Loading

0 comments on commit de959d2

Please sign in to comment.