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
9 changes: 8 additions & 1 deletion src/formbridge/forms/ui_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
"""

import logging
import re
from dataclasses import dataclass
from enum import Enum
from typing import Any
Expand All @@ -21,7 +22,13 @@ class FieldDisplayText:

@classmethod
def from_field_name(cls, name: str) -> "FieldDisplayText":
display_name = name.replace("_", " ").title()
# Labels accept both parameter_names and nominal ClassNames.
words = re.sub(r"([A-Z]+)([A-Z][a-z])", r"\1 \2", name)
words = re.sub(r"([a-z0-9])([A-Z])", r"\1 \2", words)
display_name = " ".join(
word if word.isupper() else word.title()
for word in words.replace("_", " ").split(" ")
)
return cls(
display_name=display_name,
field_label=f"{display_name}:",
Expand Down
10 changes: 9 additions & 1 deletion src/formbridge/utils/preview_formatters.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@

from objectstate import ParameterOwner

from formbridge.forms.ui_utils import FieldDisplayText


@dataclass(frozen=True, slots=True)
class PreviewLabelResolution:
Expand Down Expand Up @@ -95,7 +97,7 @@ def format_preview_value(
value: object, detail: PreviewValueDetail = PreviewValueDetail.COMPACT
) -> str | None:
"""Project display values by nominal type, without knowing config field names."""
if callable(value) and not isinstance(value, type):
if callable(value):
return type(value).__name__
return str(value)

Expand All @@ -110,6 +112,12 @@ def _format_enum(value: Enum, detail=PreviewValueDetail.COMPACT) -> str | None:
return value.name if value.value is not None else None


@format_preview_value.register(type)
def _format_type(value: type, detail=PreviewValueDetail.COMPACT) -> str:
declaration = canonical_declaration_mro(value)[0]
return FieldDisplayText.from_field_name(declaration.__name__).display_name


@format_preview_value.register(FunctionType)
@format_preview_value.register(MethodType)
def _format_function(value, detail=PreviewValueDetail.COMPACT) -> str:
Expand Down
55 changes: 55 additions & 0 deletions tests/test_preview_value_names.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
"""Class-valued choices have readable captions without changing their identity."""

from abc import ABCMeta
from dataclasses import dataclass

import pytest
from objectstate.lazy_factory import LazyDataclassFactory

from formbridge.forms.ui_utils import FieldDisplayText
from formbridge.managers.preview_formatting import ManagerPreviewFieldFormatter
from formbridge.utils.preview_formatters import PreviewFieldFormatRequest, format_preview_value


@pytest.mark.parametrize(
("name", "expected"),
[
("read_previous_step", "Read Previous Step"),
("ReadPreviousStep", "Read Previous Step"),
("SourceVoxelSpacing", "Source Voxel Spacing"),
("HTTPServer", "HTTP Server"),
("Channel", "Channel"),
],
)
def test_display_text_owns_parameter_and_class_name_spacing(name: str, expected: str) -> None:
text = FieldDisplayText.from_field_name(name)
assert text.display_name == expected
assert text.field_label == f"{expected}:"
assert text.checkbox_label == f"Enable {expected}"
assert text.group_title == expected


@pytest.mark.parametrize("metaclass", [type, ABCMeta])
def test_class_valued_choice_uses_shared_preview_formatter(metaclass: type) -> None:
choice = metaclass("ReadPreviousStep", (), {"name": "previous"})
assert format_preview_value(choice) == "Read Previous Step"
request = PreviewFieldFormatRequest("processing.input_source", choice, object)
assert ManagerPreviewFieldFormatter().format_field(request) == "input_source:Read Previous Step"
assert request.value is choice
assert choice.name == "previous"


def test_lazy_class_caption_uses_the_original_declaration() -> None:
@dataclass
class SourceVoxelSpacing:
value: int = 1

lazy = LazyDataclassFactory.make_lazy_simple(SourceVoxelSpacing, "LazyCaptionSpacing")
assert format_preview_value(lazy) == "Source Voxel Spacing"


def test_callable_preview_keeps_function_name() -> None:
def read_previous_step() -> None:
pass

assert format_preview_value(read_previous_step) == "read_previous_step"
Loading