Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
37 changes: 37 additions & 0 deletions ccflow/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -241,6 +241,34 @@ def get_widget(
# Can't use self.model_dump_json or self.model_dump because they don't expose the fallback argument
return JSON(self.__pydantic_serializer__.to_python(self, **kwargs), **(widget_kwargs or {}))

def __panel__(self):
"""Return a Panel viewable for this model.

Requires ccflow UI dependencies (panel, panel_material_ui).
"""
try:
from ccflow.ui.model import ModelViewer
except ImportError:
raise ImportError(
"panel and other optional dependencies must be installed to use ModelViewer. Pip install ccflow[full] to install all optional dependencies."
) from None

return ModelViewer(model=self)

def get_panel(self):
"""Get a Panel pane for this model.

Requires panel to be installed.
"""
try:
import panel as pn
except ImportError:
raise ImportError(
"panel and other optional dependencies must be installed to use get_panel(). Pip install ccflow[full] to install all optional dependencies."
) from None

return pn.panel(self)

@model_validator(mode="wrap")
def _base_model_validator(cls, v, handler, info):
if isinstance(v, str):
Expand Down Expand Up @@ -400,6 +428,15 @@ def models(self) -> MappingProxyType:
"""Return an immutable pointer to the models dictionary."""
return MappingProxyType(self._models)

def __panel__(self):
"""Return a Panel viewable for this registry.

Requires ccflow UI dependencies (panel, panel_material_ui).
"""
from ccflow.ui.registry import ModelRegistryViewer

return ModelRegistryViewer(self)

@classmethod
def root(cls) -> Self:
"""Return a static instance of the root registry."""
Expand Down
26 changes: 26 additions & 0 deletions ccflow/tests/test_base.py
Original file line number Diff line number Diff line change
Expand Up @@ -173,6 +173,32 @@ def test_widget(self):
),
)

def test_panel(self):
from ccflow import ModelRegistry
from ccflow.ui.model import ModelViewer
from ccflow.ui.registry import ModelRegistryViewer

m = ModelA(x="foo")
panel_obj = m.__panel__()
self.assertIsInstance(panel_obj, ModelViewer)

registry = ModelRegistry(name="test", models={"a": m})
registry_panel_obj = registry.__panel__()
self.assertIsInstance(registry_panel_obj, ModelRegistryViewer)

def test_get_panel(self):
import panel as pn

from ccflow import ModelRegistry

m = ModelA(x="foo")
panel_pane = m.get_panel()
self.assertIsInstance(panel_pane, pn.viewable.Viewable)

registry = ModelRegistry(name="test", models={"a": m})
registry_pane = registry.get_panel()
self.assertIsInstance(registry_pane, pn.viewable.Viewable)


class TestLocalRegistration(TestCase):
def test_local_class_registered_for_base_model(self):
Expand Down
Empty file added ccflow/tests/ui/__init__.py
Empty file.
66 changes: 66 additions & 0 deletions ccflow/tests/ui/test_cli.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
"""Unit tests for ccflow.ui.cli module."""

from ccflow.ui.cli import _get_ui_args_parser


class TestGetUIArgsParser:
"""Tests for _get_ui_args_parser function.

Note: Default values for hydra config args and panel server args are tested
in ccflow/tests/utils/test_hydra.py. These tests focus on viewer-specific
arguments and verifying the parser composition works correctly.
"""

def test_parser_composition(self):
"""Test parser includes args from both helper functions."""
parser = _get_ui_args_parser()
args = parser.parse_args([])

# From add_hydra_config_args
assert hasattr(args, "overrides")
assert hasattr(args, "config_path")
assert hasattr(args, "config_name")

# From add_panel_server_args
assert hasattr(args, "address")
assert hasattr(args, "port")
assert hasattr(args, "show")

# Viewer-specific
assert hasattr(args, "browser_width")
assert hasattr(args, "browser_height")
assert hasattr(args, "viewer_width")

def test_viewer_layout_defaults(self):
"""Test default values for viewer-specific arguments."""
parser = _get_ui_args_parser()
args = parser.parse_args([])

assert args.browser_width == 400
assert args.browser_height == 700
assert args.viewer_width is None

def test_viewer_layout_custom_values(self):
"""Test setting custom values for viewer layout arguments."""
parser = _get_ui_args_parser()
args = parser.parse_args(
[
"--browser-width",
"500",
"--browser-height",
"800",
"--viewer-width",
"600",
]
)

assert args.browser_width == 500
assert args.browser_height == 800
assert args.viewer_width == 600

def test_overrides_positional(self):
"""Test overrides are captured as positional arguments."""
parser = _get_ui_args_parser()
args = parser.parse_args(["key1=value1", "key2=value2"])

assert args.overrides == ["key1=value1", "key2=value2"]
Loading