Skip to content

Commit

Permalink
Add server extension using h5grove to replace jupyterlab_hdf
Browse files Browse the repository at this point in the history
  • Loading branch information
loichuder committed Jun 22, 2021
1 parent 4c81a40 commit ab66330
Show file tree
Hide file tree
Showing 12 changed files with 217 additions and 37 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ jobs:
pip install .
jupyter lab build
jupyter serverextension list 1>serverextensions 2>&1
cat serverextensions | grep "jupyterlab_hdf.*OK"
cat serverextensions | grep "jupyterlab_h5web.*OK"
jupyter labextension list 1>labextensions 2>&1
cat labextensions | grep "jupyterlab-h5web.*OK"
Expand Down
5 changes: 2 additions & 3 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ The `jlpm` command is JupyterLab's pinned version of

# Install package in development mode
pip install -e .
# Ensure that `jupyterlab_hdf` server extension is present and enabled
# Ensure that `jupyterlab_h5web` server extension is present and enabled
jupyter serverextension list

# Install dependencies
Expand Down Expand Up @@ -51,8 +51,7 @@ This extension uses `prettier` to format `*.ts` files and `black` to format

### Why a Python package for a front-end extension ?

1. To ease up installation (e.g. automatic resolution of the `jupyter_hdf` PyPI
package)
1. To ease up installation
2. To get ready to release a pre-built extension (_JupyterLab 3_)

The pre-built extension will indeed remove the need for `node`. For _JupyterLab
Expand Down
14 changes: 4 additions & 10 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -116,29 +116,23 @@ See https://github.com/silx-kit/jupyterlab-h5web/releases.
### Check the server extension

If you are seeing the frontend extension but it is not working, check that
`jupyterlab_hdf` is installed and enabled.
`jupyterlab_h5web` is installed and enabled.

It should be listed when running:

```bash
jupyter serverextension list
```

If `jupyterlab_hdf` does not appear, try to install it manually:
If `jupyterlab_h5web` does not appear or is disabled, try to enable it:

```
pip install jupyterlab_hdf
```

and to enable it:

```
jupyter serverextension enable jupyterlab_hdf
jupyter serverextension enable jupyterlab_h5web
```

### Check the frontend extension

If `jupyterlab_hdf` is installed and enabled but you are not seeing the
If `jupyterlab_h5web` is installed and enabled but you are not seeing the
frontend, check the frontend is installed:

```bash
Expand Down
2 changes: 1 addition & 1 deletion jupyter-config/jupyterlab-h5web.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"NotebookApp": {
"nbserver_extensions": {
"jupyterlab_hdf": true
"jupyterlab_h5web": true
}
}
}
23 changes: 23 additions & 0 deletions jupyterlab_h5web/__init__.py
Original file line number Diff line number Diff line change
@@ -1 +1,24 @@
from .widget import H5Web


def _jupyter_server_extension_points():
return [{"module": "jupyterlab_h5web"}]


def _load_jupyter_server_extension(server_app):
"""Registers the API handler to receive HTTP requests from the frontend extension.
Parameters
----------
server_app: jupyterlab.labapp.LabApp
JupyterLab application instance
"""
from .handlers import setup_handlers

setup_handlers(server_app.web_app, server_app.notebook_dir)
server_app.log.info(
f"Jupyterlab-h5web extension will serve HDF5 files from {server_app.notebook_dir}"
)


# For backward compatibility with notebook server - useful for Binder/JupyterHub
load_jupyter_server_extension = _load_jupyter_server_extension
71 changes: 71 additions & 0 deletions jupyterlab_h5web/handlers.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
import h5py
import tornado.web
from jupyter_server.base.handlers import APIHandler
from jupyter_server.utils import url_path_join

from h5grove.encoders import encode
from h5grove.content import create_content, ResolvedEntityContent, DatasetContent

from .utils import resolve_file_path


class BaseHandler(APIHandler):
def initialize(self, base_dir) -> None:
self.base_dir = base_dir

@tornado.web.authenticated
def get(self, file_path):
path = self.get_query_argument("path", None)
format = self.get_query_argument("format", None)

with h5py.File(resolve_file_path(file_path, self.base_dir), "r") as h5file:
content = self.get_content(h5file, path)

encoded_content_chunks, headers = encode(content, format)

for key, value in headers.items():
self.set_header(key, value)
for chunk in encoded_content_chunks:
self.write(chunk)
self.finish()

def get_content(self, h5file, path):
raise NotImplementedError


class AttributeHandler(BaseHandler):
def get_content(self, h5file, path):
content = create_content(h5file, path)
assert isinstance(content, ResolvedEntityContent)
return content.attributes()


class DataHandler(BaseHandler):
def get_content(self, h5file, path):
selection = self.get_query_argument("selection", None)

content = create_content(h5file, path)
assert isinstance(content, DatasetContent)
return content.data(selection)


class MetadataHandler(BaseHandler):
def get_content(self, h5file, path):
content = create_content(h5file, path)
return content.metadata()


def setup_handlers(web_app, base_dir):
pattern = "(.*)"
endpoints = {"attr": AttributeHandler, "data": DataHandler, "meta": MetadataHandler}

handlers = [
(
url_path_join(web_app.settings["base_url"], "h5web", endpoint, pattern),
handler,
{"base_dir": base_dir},
)
for endpoint, handler in endpoints
]

web_app.add_handlers(pattern, handlers)
21 changes: 21 additions & 0 deletions jupyterlab_h5web/utils.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
from pathlib import Path
from typing import Union


def _resolve_path(file_path: Path, base_dir: Path) -> Path:
if file_path.is_absolute():
return file_path

return base_dir / file_path


def resolve_file_path(
file_path: Union[str, Path], base_dir: Union[str, Path]
) -> Union[str, Path]:
path = file_path if isinstance(file_path, Path) else Path(file_path)
base = base_dir if isinstance(base_dir, Path) else Path(base_dir)

if isinstance(file_path, Path):
return str(_resolve_path(path, base))

return _resolve_path(path, base)
5 changes: 4 additions & 1 deletion jupyterlab_h5web/widget.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
from jupyterlab_h5web.utils import resolve_file_path
import os
import os.path
from pathlib import Path
from typing import Dict, Tuple, Union
Expand All @@ -9,10 +11,11 @@ class H5Web(DisplayObject):

def __init__(self, data: Union[str, Path], **kwargs) -> None:
# "Data" here is the path to the HDF5 file.
self.data = data
self.data = resolve_file_path(data, os.getcwd())
self.metadata = {}
if kwargs:
self.metadata.update(kwargs)
self._check_data()

def _check_data(self) -> None:
if not os.path.isfile(self.data):
Expand Down
12 changes: 1 addition & 11 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@
"postversion": "git push && git push --tags"
},
"dependencies": {
"@h5web/app": "0.0.18",
"@h5web/app": "0.0.23",
"@jupyterlab/application": "^2.0.0 || ^3.0.0",
"@jupyterlab/apputils": "~2.3.0 || ^3.0.5",
"@jupyterlab/coreutils": "^4.0.0 || ^5.0.0",
Expand Down Expand Up @@ -76,16 +76,6 @@
"style/*.css"
],
"jupyterlab": {
"discovery": {
"server": {
"managers": [
"pip"
],
"base": {
"name": "jupyterlab_hdf"
}
}
},
"extension": true
}
}
2 changes: 1 addition & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@
long_description_content_type="text/markdown",
cmdclass=cmdclass,
packages=setuptools.find_packages(),
install_requires=["jupyterlab_hdf"],
install_requires=["jupyter_server>=1.6,<2", "h5grove"],
extras_require={"full": ["hdf5plugin"]},
python_requires=">=3.6",
zip_safe=False,
Expand Down
8 changes: 4 additions & 4 deletions src/H5webApp.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import React, { useEffect, useState } from 'react';
import { App, JupyterProvider } from '@h5web/app';
import { App, H5CoreProvider as H5GroveProvider } from '@h5web/app';
import { ServerConnection } from '@jupyterlab/services';

// Render the App twice on mount as the CSS is not loaded at first render.
Expand All @@ -23,12 +23,12 @@ function H5webApp(props: { filePath: string }) {

return (
<div className="h5web-root">
<JupyterProvider
url={baseUrl.endsWith('/') ? baseUrl.slice(0, -1) : baseUrl}
<H5GroveProvider
url={`${baseUrl.endsWith('/') ? baseUrl.slice(0, -1) : baseUrl}/h5web`}
filepath={filePath}
>
<TwoRenderApp />
</JupyterProvider>
</H5GroveProvider>
</div>
);
}
Expand Down
Loading

0 comments on commit ab66330

Please sign in to comment.