Parametric Widget Creation
Dataclass-based configuration for widget creation strategies.
Module: pyqt_reactive.forms.widget_creation_config
The Widget Creation Problem
pyqt-reactive configuration is deeply nested. A pipeline configuration contains plate configurations,
which contain step configurations, which contain processing parameters—many of which are
themselves nested dataclasses. Some of these nested types are optional (Optional[DataclassType]),
requiring checkbox-gated visibility.
Building forms for this structure requires answering many questions for each field:
Is this a simple value (string, int, enum) or a nested structure?
Should it have a label? A reset button?
Does it need a container layout, and if so, horizontal or vertical?
Is it optional? Does it need a checkbox to toggle None/Instance?
Before this system, these decisions were scattered across if/elif chains with duplicated logic. Adding a new widget type (like multi-select enums) required modifying multiple code paths that had grown organically and inconsistently.
The Solution: Configuration Objects
The parametric widget creation system consolidates all these decisions into configuration
dataclasses. Each widget creation type (REGULAR, NESTED, OPTIONAL_NESTED) has a corresponding
WidgetCreationConfig that declares:
What kind of container to create
What kind of main widget to create
Whether labels, reset buttons, or checkboxes are needed
Handler functions for optional features
This follows the same pattern used across configuration registry systems: replace conditionals with configuration lookup.
Widget Creation Types
There are three fundamentally different ways to render a configuration field, distinguished by nesting and optionality:
from pyqt_reactive.forms.widget_creation_config import WidgetCreationType
class WidgetCreationType(Enum):
REGULAR = "regular" # Simple widgets (int, str, bool, enum)
NESTED = "nested" # Nested dataclass forms
OPTIONAL_NESTED = "optional_nested" # Optional[Dataclass] with checkbox
Each type has a corresponding WidgetCreationConfig dataclass:
@dataclass
class WidgetCreationConfig:
layout_type: str # "horizontal" or "vertical"
is_nested: bool # Whether creates sub-form
create_container: WidgetOperationHandler # Container widget factory
setup_layout: Optional[WidgetOperationHandler] # Layout configuration
create_main_widget: WidgetOperationHandler # Main widget factory
needs_label: bool # Show field label
needs_reset_button: bool # Show reset button
needs_unwrap_type: bool # Unwrap Optional[T] to T
is_optional: bool = False # Has None/Instance toggle
needs_checkbox: bool = False # Show optional checkbox
create_title_widget: Optional[OptionalTitleHandler] = None
connect_checkbox_logic: Optional[CheckboxLogicHandler] = None
ParameterFormManager ABC
The widget creation system needs a consistent interface to the form manager. This ABC
defines that interface, inspired by React’s component model. Like React components,
form managers maintain state (parameters, nested_managers, widgets) and
provide methods to mutate that state (update_parameter, reset_parameter).
The React analogy is deliberate: parameter forms are essentially hierarchical
UI components with controlled inputs. The create_widget method is analogous to
React’s render()—it produces UI elements based on current state. The
_apply_to_nested_managers method enables recursive traversal, similar to React’s
component tree:
from pyqt_reactive.forms.widget_creation_types import ParameterFormManager
class ParameterFormManager(ABC):
"""React-quality reactive form manager interface."""
# State (like React component state)
parameters: Dict[str, Any]
nested_managers: Dict[str, Any]
widgets: Dict[str, Any]
# State mutations (like setState)
@abstractmethod
def update_parameter(self, param_name: str, value: Any) -> None: ...
@abstractmethod
def reset_parameter(self, param_name: str) -> None: ...
# Widget creation (like render)
@abstractmethod
def create_widget(self, param_name: str, param_type: Type,
current_value: Any, widget_id: str) -> Any: ...
# Component tree traversal
@abstractmethod
def _apply_to_nested_managers(self, callback: Callable) -> None: ...
Type Definitions
The handler functions have complex signatures because they need access to everything the form manager knows about the field being rendered. TypedDicts and type aliases provide documentation and type checking for these signatures:
Handler type aliases for type safety:
# Main widget operation handler
WidgetOperationHandler = Callable[
[ParameterFormManager, ParameterInfo, DisplayInfo, FieldIds,
Any, Optional[Type], ...],
Any
]
# Optional title widget handler
OptionalTitleHandler = Callable[
[ParameterFormManager, ParameterInfo, DisplayInfo, FieldIds,
Any, Optional[Type]],
Dict[str, Any]
]
# Checkbox toggle logic handler
CheckboxLogicHandler = Callable[
[ParameterFormManager, ParameterInfo, Any, Any, Any, Any, Any, Type],
None
]
Helper TypedDicts:
class DisplayInfo(TypedDict, total=False):
field_label: str
checkbox_label: str
description: str
class FieldIds(TypedDict, total=False):
widget_id: str
optional_checkbox_id: str
Configuration Registry
The _WIDGET_CREATION_CONFIG dict maps types to configurations:
_WIDGET_CREATION_CONFIG = {
WidgetCreationType.REGULAR: WidgetCreationConfig(
layout_type="horizontal",
is_nested=False,
create_container=_create_regular_container,
setup_layout=None,
create_main_widget=_create_regular_widget,
needs_label=True,
needs_reset_button=True,
needs_unwrap_type=False,
),
WidgetCreationType.NESTED: WidgetCreationConfig(
layout_type="vertical",
is_nested=True,
create_container=_create_nested_container,
setup_layout=_setup_nested_layout,
create_main_widget=_create_nested_form,
needs_label=False,
needs_reset_button=False,
needs_unwrap_type=True,
),
WidgetCreationType.OPTIONAL_NESTED: WidgetCreationConfig(
layout_type="vertical",
is_nested=True,
is_optional=True,
needs_checkbox=True,
create_container=_create_optional_container,
setup_layout=_setup_nested_layout,
create_main_widget=_create_nested_form,
create_title_widget=_create_optional_title_widget,
connect_checkbox_logic=_connect_optional_checkbox_logic,
needs_label=False,
needs_reset_button=False,
needs_unwrap_type=True,
),
}
Handler Functions
Handlers encapsulate widget creation logic:
def _create_nested_form(manager, param_info, display_info, field_ids,
current_value, unwrapped_type, **kwargs) -> Any:
"""Create nested form and store in manager.nested_managers."""
nested_manager = manager._create_nested_form_inline(
param_info.name, unwrapped_type, current_value
)
manager.nested_managers[param_info.name] = nested_manager
return nested_manager.build_form()
def _create_optional_title_widget(manager, param_info, display_info,
field_ids, current_value, unwrapped_type):
"""Create checkbox + title + reset button for optional dataclass."""
# Returns (title_widget, checkbox) tuple
...
Usage Pattern
The ParameterFormManager uses the config for type-dispatched widget creation:
def _create_parameter_widget(self, param_info, param_type, current_value):
# Determine widget type
widget_type = self._classify_widget_type(param_type)
# Get configuration
config = _WIDGET_CREATION_CONFIG[widget_type]
# Execute creation pipeline
container = config.create_container(self, param_info, ...)
if config.setup_layout:
config.setup_layout(self, param_info, container, ...)
widget = config.create_main_widget(self, param_info, ...)
if config.needs_label:
self._add_label(container, param_info)
if config.needs_reset_button:
self._add_reset_button(container, param_info)
See Also
Widget Protocol System - ABC contracts for widget operations
Field Change Dispatcher Architecture - Dispatches changes from created widgets
Parameter Form Service Architecture - Service architecture using these configs
AbstractManagerWidget Architecture - ABC that orchestrates widget creation