From bb065c477975581e116004dd38a376b034a26c6e Mon Sep 17 00:00:00 2001 From: sbkelley Date: Sun, 7 Dec 2025 11:01:14 -0500 Subject: [PATCH] Improve make_pane docstring and function signatures --- .../interface/molecule/settings_block/pane.py | 74 ++++++++++++------- .../molecule/settings_block/pane.pyi | 11 +-- .../molecule/settings_block/settings_block.py | 30 ++++---- 3 files changed, 69 insertions(+), 46 deletions(-) diff --git a/src/obs_ctl/interface/molecule/settings_block/pane.py b/src/obs_ctl/interface/molecule/settings_block/pane.py index 1607f6d..8855c6b 100644 --- a/src/obs_ctl/interface/molecule/settings_block/pane.py +++ b/src/obs_ctl/interface/molecule/settings_block/pane.py @@ -67,7 +67,7 @@ class SettingsWidget(Enum): type MakeWidgetOutputType = tuple[str, SettingsWidget, Any, ParamsType] -def make_settings_widget(name: str, type: SettingsWidget, default: Any, *args, **kwargs) -> MakeWidgetOutputType: +def make_pane_widget(name: str, type: SettingsWidget, default: Any, *args, **kwargs) -> MakeWidgetOutputType: return (name, type, default, (args, kwargs)) @@ -76,34 +76,58 @@ type KWArgsType = Mapping[str, Any] type ParamsType = tuple[ArgsType, KWArgsType] | None type ParamsListType = Sequence[ParamsType] type LoopListType = Sequence[MakeWidgetOutputType] -def make_pane_subclass( +def make_pane( cls_name: str, - settings_name_list: list[str] | LoopListType, + *args, + names_list: list[str] | LoopListType | None=None, settings_type_list: list[SettingsWidget] | None=None, - settings_default_list: list[Any] | None=None, - settings_type_params_list: ParamsListType | None=None, + defaults_list: list[Any] | None=None, + params_list: ParamsListType | None=None, ) -> type[PaneBase]: """Creates a new TabPane subclass with cls_name as its name. - - settings_list should be a list of names for settings. - settings_type_list should be a list of pane.SettingsType members - settings_default_list should be a list of defaults, for the settings. - - Returned widget will have appropriate messages for each input field. - - For example, pane_factory("Settings", ["record_path"], [pane.SettingsType.STRING], [""]) - will return a TabPane with a Label and an Input box, a reactive attribute "record_path", - a watch_record_path function to update the Input value, and an on_record_path_changed - function, which emits a RecordPathChanged message containing a value when the user - submits the input.""" - row_height = 1 - if settings_type_list is None and settings_default_list is None and settings_type_params_list is None: - loop_list: LoopListType = settings_name_list # type: ignore + + All the arguments must be keyword arguments, or all of the arguments + must be positional pane.make_pane_widget() results. + + If provided: + **name_list** must be a list of names for settings. + **settings_type_list** must be a list of pane.SettingsType members + **default_list** must be a list of defaults, for the settings. + **params_list** is optionally a tuple of args and kwarg, passed into + the constructor for the particular Textual Widget created. + eg: (("foo", "bar"), {"buz":5}) + + Returned widget will have appropriate messages for each input field. + + For example: + ```python + >>> pane_factory("Settings", + name_list=["record_path"], + settings_type_list=[pane.SettingsType.STRING], + defaults_list=[""]) + ... [TabPane Instance] + >>> pane_factory("Settings", + pane.make_settings_widget( + "record_path", + pane.SettingsWidget.STRING, + "")) + ... [Equivalent (But Not Identical) TabPane Instance] + ``` + The returned a TabPane will have: + A reactive attribute "record_path" + A watch_record_path function to update the Input's value + An on_record_path_changed function, which (upon Input submission) emits ... + A RecordPathChanged message containing the submitted value. + """ + # Detect whether this is a "looplist" style input or a parameters style input + ROW_HEIGHT = 1 + if names_list is not None: + params_list = params_list if params_list is not None else ( ((), dict()), ) + loop_list: LoopListType = list(zip_longest(names_list, settings_type_list, defaults_list, params_list)) # type: ignore else: - settings_type_params_list = settings_type_params_list if settings_type_params_list is not None else ( ((), dict()), ) - loop_list: LoopListType = list(zip_longest(settings_name_list, settings_type_list, settings_default_list, settings_type_params_list)) # type: ignore + loop_list: LoopListType = args # type: ignore - DEFAULT_CSS = f"Grid {{grid-size:2;grid-rows:{row_height};min-height:{len(loop_list)*row_height}}}" + DEFAULT_CSS = f"Grid {{grid-size:2;grid-rows:{ROW_HEIGHT};min-height:{len(loop_list)*ROW_HEIGHT}}}" def make_watch_reactive(watch_id: str): def watch_func(self: PaneBase, value: Any): @@ -144,7 +168,7 @@ def make_pane_subclass( new_dict: dict[str, Any] = dict() for setting_name, setting_type, setting_default, _ in loop_list: control_setting_id: str = f"{setting_type.widget.__name__.lower()}_{setting_name}" - SettingChanged_name: str = f"{snake_to_camel(setting_name)}Changed" + SettingChanged_name: str = f"{_snake_to_camel(setting_name)}Changed" watch_setting_name: str = f"watch_{setting_name}" on_setting_changed_name: str = f"on_{setting_name}_changed" # For each desired setting (e.g. 'record_path'): @@ -172,5 +196,5 @@ def make_pane_subclass( new_pane: type[PaneBase] = type(cls_name, (PaneBase,), new_dict) return new_pane -def snake_to_camel(string: str): +def _snake_to_camel(string: str): return str.join("", [str.capitalize(x) for x in string.split("_")]) \ No newline at end of file diff --git a/src/obs_ctl/interface/molecule/settings_block/pane.pyi b/src/obs_ctl/interface/molecule/settings_block/pane.pyi index 0bc115c..9bc72f0 100644 --- a/src/obs_ctl/interface/molecule/settings_block/pane.pyi +++ b/src/obs_ctl/interface/molecule/settings_block/pane.pyi @@ -33,18 +33,19 @@ type KWArgsType = Mapping[str, Any] type ParamsType = tuple[ArgsType, KWArgsType] type ParamsListType = Sequence[ParamsType | None] @overload -def make_pane_subclass( +def make_pane( cls_name: str, - settings_list: Sequence[str], + *, + names_list: Sequence[str], settings_type_list: Sequence[SettingsWidget], defaults_list: Sequence[Any], params_list: ParamsListType | None=None, ) -> type[PaneBase]: ... @overload -def make_pane_subclass( +def make_pane( cls_name: str, - settings_list: Sequence[MakeWidgetOutputType], + *args: MakeWidgetOutputType, ) -> type[PaneBase]: ... type MakeWidgetOutputType = tuple[str, SettingsWidget, Any, ParamsType] -def make_settings_widget(name: str, type: SettingsWidget, default: Any, *args: Any, **kwargs: Any) -> MakeWidgetOutputType: ... \ No newline at end of file +def make_pane_widget(name: str, type: SettingsWidget, default: Any, *args: Any, **kwargs: Any) -> MakeWidgetOutputType: ... \ No newline at end of file diff --git a/src/obs_ctl/interface/molecule/settings_block/settings_block.py b/src/obs_ctl/interface/molecule/settings_block/settings_block.py index f6aeb94..2db8501 100644 --- a/src/obs_ctl/interface/molecule/settings_block/settings_block.py +++ b/src/obs_ctl/interface/molecule/settings_block/settings_block.py @@ -6,29 +6,27 @@ from . import pane logger = getLogger(__name__) -OBSSettingsPane = pane.make_pane_subclass( +OBSSettingsPane = pane.make_pane( "OBSSettingsPane", - ["record_path"], - [pane.SettingsWidget.STRING], - [""], - [((), {"compact":True}),] + names_list=["record_path"], + settings_type_list=[pane.SettingsWidget.STRING], + defaults_list=[""], + params_list=[((), {"compact":True}),] ) -OBSWebSocketPane = pane.make_pane_subclass( +OBSWebSocketPane = pane.make_pane( "OBSWebSocketPane", - [ - pane.make_settings_widget("host", pane.SettingsWidget.STRING, "localhost", compact=True), - pane.make_settings_widget("port", pane.SettingsWidget.INT, 4455, compact=True), - pane.make_settings_widget("password", pane.SettingsWidget.STRING, "", compact=True), - pane.make_settings_widget("timeout", pane.SettingsWidget.FLOAT, 1.0, compact=True) - ] + pane.make_pane_widget("host", pane.SettingsWidget.STRING, "localhost", compact=True), + pane.make_pane_widget("port", pane.SettingsWidget.INT, 4455, compact=True), + pane.make_pane_widget("password", pane.SettingsWidget.STRING, "", compact=True), + pane.make_pane_widget("timeout", pane.SettingsWidget.FLOAT, 1.0, compact=True), ) -OBSCTLPane = pane.make_pane_subclass( +OBSCTLPane = pane.make_pane( "OBSCTLPane", - ["nothing"], - [pane.SettingsWidget.STRING], - ["zilch"] + names_list=["nothing"], + settings_type_list=[pane.SettingsWidget.STRING], + defaults_list=["zilch"] ) class SettingsBlock(Container): -- 2.47.3