Source code for pyqt_reactive.forms.widget_dispatcher
"""
Widget dispatcher with fail-loud ABC checking.
Replaces duck typing (hasattr checks) with explicit isinstance checks against ABCs.
All methods fail loud if widget doesn't implement required ABC.
Design Philosophy:
- Explicit over implicit
- Fail-loud over fail-silent
- Type-safe over duck-typed
"""
from typing import Any, Callable
from pyqt_reactive.protocols import (
ValueGettable, ValueSettable, PlaceholderCapable,
RangeConfigurable, EnumSelectable, ChangeSignalEmitter
)
[docs]
class WidgetDispatcher:
"""
ABC-based widget dispatch - NO DUCK TYPING.
Replaces hasattr checks with explicit isinstance checks.
Fails loud if widget doesn't implement required ABC.
Example:
# BEFORE (duck typing):
if hasattr(widget, 'get_value'):
value = widget.get_value()
# AFTER (ABC-based):
value = WidgetDispatcher.get_value(widget) # Raises TypeError if not ValueGettable
"""
[docs]
@staticmethod
def get_value(widget: Any) -> Any:
"""
Get value from widget using explicit ABC check.
Args:
widget: The widget to get value from
Returns:
The widget's current value
Raises:
TypeError: If widget doesn't implement ValueGettable ABC
"""
if not isinstance(widget, ValueGettable):
raise TypeError(
f"Widget {type(widget).__name__} does not implement ValueGettable ABC. "
f"Add ValueGettable to widget's base classes and implement get_value() method."
)
return widget.get_value()
[docs]
@staticmethod
def set_value(widget: Any, value: Any) -> None:
"""
Set value on widget using explicit ABC check.
Args:
widget: The widget to set value on
value: The value to set
Raises:
TypeError: If widget doesn't implement ValueSettable ABC
"""
if not isinstance(widget, ValueSettable):
raise TypeError(
f"Widget {type(widget).__name__} does not implement ValueSettable ABC. "
f"Add ValueSettable to widget's base classes and implement set_value() method."
)
widget.set_value(value)
[docs]
@staticmethod
def set_placeholder(widget: Any, text: str) -> None:
"""
Set placeholder using explicit ABC check.
Args:
widget: The widget to set placeholder on
text: The placeholder text
Raises:
TypeError: If widget doesn't implement PlaceholderCapable ABC
"""
if not isinstance(widget, PlaceholderCapable):
raise TypeError(
f"Widget {type(widget).__name__} does not implement PlaceholderCapable ABC. "
f"Add PlaceholderCapable to widget's base classes and implement set_placeholder() method."
)
widget.set_placeholder(text)
[docs]
@staticmethod
def configure_range(widget: Any, minimum: float, maximum: float) -> None:
"""
Configure range using explicit ABC check.
Args:
widget: The widget to configure
minimum: Minimum value
maximum: Maximum value
Raises:
TypeError: If widget doesn't implement RangeConfigurable ABC
"""
if not isinstance(widget, RangeConfigurable):
raise TypeError(
f"Widget {type(widget).__name__} does not implement RangeConfigurable ABC. "
f"Add RangeConfigurable to widget's base classes and implement configure_range() method."
)
widget.configure_range(minimum, maximum)
[docs]
@staticmethod
def set_enum_options(widget: Any, enum_type: type) -> None:
"""
Set enum options using explicit ABC check.
Args:
widget: The widget to configure
enum_type: The Enum class
Raises:
TypeError: If widget doesn't implement EnumSelectable ABC
"""
if not isinstance(widget, EnumSelectable):
raise TypeError(
f"Widget {type(widget).__name__} does not implement EnumSelectable ABC. "
f"Add EnumSelectable to widget's base classes and implement set_enum_options() method."
)
widget.set_enum_options(enum_type)
[docs]
@staticmethod
def get_selected_enum(widget: Any) -> Any:
"""
Get selected enum using explicit ABC check.
Args:
widget: The widget to get selection from
Returns:
The selected enum value
Raises:
TypeError: If widget doesn't implement EnumSelectable ABC
"""
if not isinstance(widget, EnumSelectable):
raise TypeError(
f"Widget {type(widget).__name__} does not implement EnumSelectable ABC. "
f"Add EnumSelectable to widget's base classes."
)
return widget.get_selected_enum()
[docs]
@staticmethod
def connect_change_signal(widget: Any, callback: Callable[[Any], None]) -> None:
"""
Connect change signal using explicit ABC check.
Args:
widget: The widget to connect signal on
callback: Callback function receiving new value
Raises:
TypeError: If widget doesn't implement ChangeSignalEmitter ABC
"""
if not isinstance(widget, ChangeSignalEmitter):
raise TypeError(
f"Widget {type(widget).__name__} does not implement ChangeSignalEmitter ABC. "
f"Add ChangeSignalEmitter to widget's base classes and implement "
f"connect_change_signal() method."
)
widget.connect_change_signal(callback)
[docs]
@staticmethod
def disconnect_change_signal(widget: Any, callback: Callable[[Any], None]) -> None:
"""
Disconnect change signal using explicit ABC check.
Args:
widget: The widget to disconnect signal from
callback: Callback function to disconnect
Raises:
TypeError: If widget doesn't implement ChangeSignalEmitter ABC
"""
if not isinstance(widget, ChangeSignalEmitter):
raise TypeError(
f"Widget {type(widget).__name__} does not implement ChangeSignalEmitter ABC."
)
widget.disconnect_change_signal(callback)