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
57 changes: 41 additions & 16 deletions src/techui_builder/generate.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import logging
import os
from collections import defaultdict
from collections.abc import Mapping, Sequence
from dataclasses import dataclass, field
from pathlib import Path

Expand Down Expand Up @@ -164,13 +165,7 @@ def _get_group_dimensions(self, widget_list: list[EmbeddedDisplay | ActionButton
max(x_list) + max(width_list) + self.group_padding,
)

def _create_widget(
self, component: Entity
) -> EmbeddedDisplay | ActionButton | None:
# if statement below is check if the suffix is
# missing from the component description. If
# not missing, use as name of widget, if missing,
# use type as name.
def _initialise_name_suffix(self, component: Entity) -> tuple[str, str, str | None]:
if component.M is not None:
name: str = component.M
suffix: str = component.M
Expand All @@ -184,23 +179,24 @@ def _create_widget(
suffix = ""
suffix_label = None

try:
scrn_mapping = self.techui_support[component.type]
except KeyError:
LOGGER.warning(
f"No available widget for {component.type} in screen \
{self.screen_name}. Skipping..."
)
return None
return (name, suffix, suffix_label)

def _is_list_of_dicts(self, scrn_mapping: Mapping) -> bool:
return isinstance(scrn_mapping, Sequence) and all(
isinstance(scrn, Mapping) for scrn in scrn_mapping
)

def _allocate_widget(
self, scrn_mapping: Mapping, component: Entity
) -> EmbeddedDisplay | ActionButton | None | list[EmbeddedDisplay | ActionButton]:
name, suffix, suffix_label = self._initialise_name_suffix(component)
# Get relative path to screen
scrn_path = self.support_path.joinpath(f"bob/{scrn_mapping['file']}")
LOGGER.debug(f"Screen path: {scrn_path}")

# Path of screen relative to data/ so it knows where to open the file from
data_scrn_path = scrn_path.relative_to(self.synoptic_dir, walk_up=True)

# Get dimensions of screen from TechUI repository
if scrn_mapping["type"] == "embedded":
height, width = self._get_screen_dimensions(str(scrn_path))
new_widget = Widget.EmbeddedDisplay(
Expand Down Expand Up @@ -253,6 +249,32 @@ def _create_widget(
new_widget.version("2.0.0")
return new_widget

def _create_widget(
self, component: Entity
) -> EmbeddedDisplay | ActionButton | None | list[EmbeddedDisplay | ActionButton]:
# if statement below is check if the suffix is
# missing from the component description. If
# not missing, use as name of widget, if missing,
# use type as name.
new_widget = []

try:
scrn_mapping = self.techui_support[component.type]
except KeyError:
LOGGER.warning(
f"No available widget for {component.type} in screen \
{self.screen_name}. Skipping..."
)
return None

if self._is_list_of_dicts(scrn_mapping):
for value in scrn_mapping:
new_widget.append(self._allocate_widget(value, component))
else:
new_widget = self._allocate_widget(scrn_mapping, component)

return new_widget

def layout_widgets(self, widgets: list[EmbeddedDisplay | ActionButton]):
group_spacing: int = 30
max_group_height: int = 800
Expand Down Expand Up @@ -334,6 +356,9 @@ def build_groups(self):
new_widget = self._create_widget(component=component)
if new_widget is None:
continue
if isinstance(new_widget, list):
self.widgets.extend(new_widget)
continue
self.widgets.append(new_widget)

if self.widgets == []:
Expand Down
7 changes: 5 additions & 2 deletions src/techui_builder/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -130,7 +130,7 @@ class TechUi(BaseModel):


"""
Ibek mapping models
techui_support mapping models
"""

BobPath = Annotated[
Expand All @@ -151,7 +151,10 @@ class GuiComponentEntry(BaseModel):
model_config = ConfigDict(extra="forbid")


class GuiComponents(RootModel[dict[str, GuiComponentEntry]]):
GuiComponentUnion = list[GuiComponentEntry] | GuiComponentEntry


class GuiComponents(RootModel[dict[str, GuiComponentUnion]]):
pass


Expand Down
77 changes: 77 additions & 0 deletions tests/test_generate.py
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,25 @@ def test_generator_create_widget_keyerror(generator, caplog):
)


def test_generator_create_widget_is_list_of_dicts(generator):
generator._get_screen_dimensions = Mock(return_value=(800, 1280))
generator._is_list_of_dicts = Mock(return_value=True)
generator._allocate_widget = Mock(
return_value=Widget.EmbeddedDisplay(
name="X", file="", x=0, y=0, width=205, height=120
)
)
generator.screen_name = "test"
component = Entity(
type="ADAravis.aravisCamera", P="BL23B-DI-MOD-02", desc=None, M=None, R="CAM:"
)
widget = generator._create_widget(component=component)
for value in widget:
assert str(value) == str(
Widget.EmbeddedDisplay(name="X", file="", x=0, y=0, width=205, height=120)
)


def test_generator_create_widget_embedded(generator):
generator._get_screen_dimensions = Mock(return_value=(800, 1280))
component = Entity(
Expand All @@ -139,7 +158,65 @@ def test_generator_create_widget_embedded(generator):
widget = generator._create_widget(
component=component,
)
control_widget = Path("tests/test_files/widget.xml")
with open(control_widget) as f:
xml_content = f.read()

assert str(widget) == xml_content


def test_generator_initialise_name_suffix_m(generator):
component = Entity(type="test", P="TEST", desc=None, M="T1", R=None)

name, suffix, suffix_label = generator._initialise_name_suffix(component)

assert name == "T1"
assert suffix == "T1"
assert suffix_label == "M"


def test_generator_initialise_name_suffix_r(generator):
component = Entity(type="test", P="TEST", desc=None, M=None, R="T1")

name, suffix, suffix_label = generator._initialise_name_suffix(component)

assert name == "T1"
assert suffix == "T1"
assert suffix_label == "R"


def test_generator_initialise_name_suffix_none(generator):
component = Entity(type="test", P="TEST", desc=None, M=None, R=None)

name, suffix, suffix_label = generator._initialise_name_suffix(component)

assert name == "test"
assert suffix == ""
assert suffix_label is None


def test_generator_is_list_of_dicts(generator):
list_of_dicts = [{"a": 1}, {"b": 2}]
assert generator._is_list_of_dicts(list_of_dicts) is True


def test_generator_is_list_of_dicts_not(generator):
not_list_of_dicts = {"a": 1}
assert generator._is_list_of_dicts(not_list_of_dicts) is False


def test_generator_allocate_widget(generator):
generator._initilise_name_suffix = Mock(return_value=("CAM:", "CAM:", "R"))

scrn_mapping = {
"file": "ADAravis/ADAravis_summary.bob",
"prefix": "$(P)$(R)",
"type": "embedded",
}
component = Entity(
type="ADAravis.aravisCamera", P="BL23B-DI-MOD-02", desc=None, M=None, R="CAM:"
)
widget = generator._allocate_widget(scrn_mapping, component)
control_widget = Path("tests/test_files/widget.xml")

with open(control_widget) as f:
Expand Down
29 changes: 28 additions & 1 deletion tests/test_models.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
import pytest

from techui_builder.models import Beamline, Component
from techui_builder.models import (
Beamline,
Component,
GuiComponentEntry,
GuiComponents,
)


@pytest.fixture
Expand All @@ -13,6 +18,13 @@ def component() -> Component:
return Component(prefix="BL01T-EA-TEST-02", desc="Test Device")


@pytest.fixture
def gui_components() -> GuiComponentEntry:
return GuiComponentEntry(
file="digitelMpc/digitelMpcIonp.bob", prefix="$(P)", type="embedded"
)


# @pytest.mark.parametrize("beamline,expected",[])
def test_beamline_object(beamline: Beamline):
assert beamline.short_dom == "t01"
Expand All @@ -39,3 +51,18 @@ def test_component_repr(component: Component):
def test_component_bad_prefix():
with pytest.raises(ValueError):
Component(prefix="Test 2", desc="BAD_PREFIX")


def test_gui_component_entry(gui_components: GuiComponentEntry):
assert gui_components.file == "digitelMpc/digitelMpcIonp.bob"
assert gui_components.prefix == "$(P)"
assert gui_components.type == "embedded"


def test_gui_components_object(gui_components: GuiComponentEntry):
gc = GuiComponents({"digitelMpc.digitelMpcIonp": [gui_components]})
entry = gc.root["digitelMpc.digitelMpcIonp"][0] # type: ignore
assert entry.file == "digitelMpc/digitelMpcIonp.bob"

assert entry.prefix == "$(P)"
assert entry.type == "embedded"