Skip to content

Commit

Permalink
enhanced config, added viewer contrib Panel
Browse files Browse the repository at this point in the history
  • Loading branch information
forman committed Nov 8, 2024
1 parent d5003d6 commit c505e1c
Show file tree
Hide file tree
Showing 8 changed files with 117 additions and 26 deletions.
10 changes: 6 additions & 4 deletions examples/serve/demo3/config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,12 @@ DataStores:
Path: compute_indexes.py
Function: compute_indexes

Viewer:
Augmentation:
Path: ""
Extensions:
- my_viewer_ext.ext

Styles:
- Identifier: S2L2A
ColorMappings:
Expand Down Expand Up @@ -73,10 +79,6 @@ Styles:
Variable: B02
ValueRange: [0., 0.25]

Viewer:
Extensions:
- Path: my_viewer_ext.ext

ServiceProvider:
ProviderName: "Brockmann Consult GmbH"
ProviderSite: "https://www.brockmann-consult.de"
Expand Down
10 changes: 8 additions & 2 deletions examples/serve/demo3/my_viewer_ext/my_panel_1.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
from dashipy import Component, Input, State, Output
from dashipy.components import Box, Dropdown, Checkbox, Typography
from dashipy.demo.contribs import Panel
from dashipy.demo.context import Context

from xcube.webapi.viewer.contrib import Panel
from xcube.webapi.viewer.contrib import get_datasets_ctx
from xcube.server.api import Context


panel = Panel(__name__, title="Panel B")
Expand Down Expand Up @@ -66,6 +68,9 @@ def update_info_text(
color: int = 0,
info_text: str = "",
) -> str:
ds_ctx = get_datasets_ctx(ctx)
ds_configs = ds_ctx.get_dataset_configs()

opaque = opaque or False
color = color if color is not None else 0
return (
Expand All @@ -74,4 +79,5 @@ def update_info_text(
f" it {'is' if opaque else 'is not'} opaque."
f" The length of the last info text"
f" was {len(info_text or "")}."
f" The number of datasets is {len(ds_configs)}."
)
46 changes: 41 additions & 5 deletions test/webapi/viewer/test_config.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
from xcube.webapi.viewer.config import CONFIG_SCHEMA


class ViewerConfigTest(unittest.TestCase):
class ViewerConfigurationTest(unittest.TestCase):
# noinspection PyMethodMayBeStatic
def test_validate_instance_ok(self):
CONFIG_SCHEMA.validate_instance({})
Expand All @@ -27,10 +27,6 @@ def test_validate_instance_ok(self):
{
"Viewer": {
"Configuration": {"Path": "s3://xcube-viewer-app/bc/dev/viewer/"},
"Extensions": [
{"Path": "my_ext_1"},
{"Path": "my_ext_2"},
],
}
}
)
Expand All @@ -41,6 +37,7 @@ def test_validate_instance_fails(self):
CONFIG_SCHEMA.validate_instance(
{
"Viewer": {
# Missing required "Path"
"Config": {},
}
}
Expand All @@ -50,10 +47,49 @@ def test_validate_instance_fails(self):
CONFIG_SCHEMA.validate_instance(
{
"Viewer": {
# Forbidden "Title"
"Configuration": {
"Path": "s3://xcube-viewer-app/bc/dev/viewer/",
"Title": "Test!",
},
}
}
)


class ViewerAugmentationTest(unittest.TestCase):
# noinspection PyMethodMayBeStatic
def test_validate_instance_ok(self):
CONFIG_SCHEMA.validate_instance({})
CONFIG_SCHEMA.validate_instance({"Viewer": {}})
CONFIG_SCHEMA.validate_instance(
{
"Viewer": {
"Augmentation": {
"Extensions": ["my_feature_1.ext", "my_feature_2.ext"]
},
}
}
)
CONFIG_SCHEMA.validate_instance(
{
"Viewer": {
"Augmentation": {
"Path": "home/ext",
"Extensions": ["my_feature_1.ext", "my_feature_2.ext"],
},
}
}
)

# noinspection PyMethodMayBeStatic
def test_validate_instance_fails(self):
with pytest.raises(jsonschema.exceptions.ValidationError):
CONFIG_SCHEMA.validate_instance(
{
"Viewer": {
# Missing required "Extensions"
"Augmentation": {},
}
}
)
15 changes: 9 additions & 6 deletions xcube/webapi/viewer/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,21 +13,24 @@
additional_properties=False,
)

EXTENSION_SCHEMA = JsonObjectSchema(
EXTENSIONS_SCHEMA = JsonArraySchema(
items=STRING_SCHEMA,
min_items=1,
)

AUGMENTATION_SCHEMA = JsonObjectSchema(
properties=dict(
Path=STRING_SCHEMA,
Extensions=EXTENSIONS_SCHEMA,
),
required=["Extensions"],
additional_properties=False,
)

EXTENSIONS_SCHEMA = JsonArraySchema(
items=EXTENSION_SCHEMA,
)

VIEWER_SCHEMA = JsonObjectSchema(
properties=dict(
Configuration=CONFIGURATION_SCHEMA,
Extensions=EXTENSIONS_SCHEMA,
Augmentation=AUGMENTATION_SCHEMA,
),
additional_properties=False,
)
Expand Down
45 changes: 36 additions & 9 deletions xcube/webapi/viewer/context.py
Original file line number Diff line number Diff line change
@@ -1,16 +1,23 @@
# Copyright (c) 2018-2024 by xcube team and contributors
# Permissions are hereby granted under the terms of the MIT License:
# https://opensource.org/licenses/MIT.

from contextlib import contextmanager
from functools import cached_property
from pathlib import Path
from typing import Optional
from collections.abc import Mapping
import sys

from dashipy import Extension
from dashipy import ExtensionContext
import fsspec

from xcube.constants import LOG
from xcube.server.api import Context
from xcube.webapi.common.context import ResourcesContext
from xcube.webapi.viewer.contrib import Panel

Extension.add_contrib_point("panels", Panel)


class ViewerContext(ResourcesContext):
Expand All @@ -22,14 +29,20 @@ def __init__(self, server_ctx: Context):

def on_update(self, prev_context: Optional[Context]):
super().on_update(prev_context)
if "Viewer" not in self.config:
return None
extension_infos: list[dict] = self.config["Viewer"].get("Extensions")
if extension_infos:
extension_modules = [e["Path"] for e in extension_infos if "Path" in e]
print("----------------------->", extension_modules)
extensions = ExtensionContext.load_extensions(extension_modules)
self.ext_ctx = ExtensionContext(self.server_ctx, extensions)
viewer_config: dict = self.config.get("Viewer")
if viewer_config:
augmentation: dict | None = viewer_config.get("Augmentation")
if augmentation:
extension_refs: list[str] = augmentation["Extensions"]
path: Path | None = None
if "Path" in augmentation:
path = Path(augmentation["Path"])
if not path.is_absolute():
path = Path(self.base_dir) / path
with prepend_sys_path(path):
self.ext_ctx = ExtensionContext.load(
self.server_ctx, extension_refs
)

@cached_property
def config_items(self) -> Optional[Mapping[str, bytes]]:
Expand All @@ -45,3 +58,17 @@ def config_path(self) -> Optional[str]:
self.config["Viewer"].get("Configuration", {}),
"'Configuration' item of 'Viewer'",
)


@contextmanager
def prepend_sys_path(path: Path | str | None):
prev_sys_path = None
if path is not None:
LOG.warning(f"temporarily prepending '{path}' to sys.path")
prev_sys_path = sys.path
sys.path = [str(path)] + sys.path
try:
yield path is not None
finally:
if prev_sys_path:
sys.path = prev_sys_path
3 changes: 3 additions & 0 deletions xcube/webapi/viewer/contrib/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
from .helpers import get_datasets_ctx

from .panel import Panel
6 changes: 6 additions & 0 deletions xcube/webapi/viewer/contrib/helpers.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
from xcube.webapi.datasets.context import DatasetsContext
from xcube.server.api import Context


def get_datasets_ctx(ctx: Context) -> DatasetsContext:
return ctx.get_api_ctx("datasets")
8 changes: 8 additions & 0 deletions xcube/webapi/viewer/contrib/panel.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
from dashipy import Contribution


class Panel(Contribution):
"""Panel contribution"""

def __init__(self, name: str, title: str | None = None):
super().__init__(name, visible=False, title=title)

0 comments on commit c505e1c

Please sign in to comment.