Skip to content

Commit 477084d

Browse files
committed
refactor(component editor): Use local functions instead of lamba functions
1 parent adbb29f commit 477084d

File tree

3 files changed

+52
-50
lines changed

3 files changed

+52
-50
lines changed

ardupilot_methodic_configurator/frontend_tkinter_component_editor.py

Lines changed: 16 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@
1818
from logging import basicConfig as logging_basicConfig
1919
from logging import getLevelName as logging_getLevelName
2020
from tkinter import ttk
21-
from typing import Callable, Optional, Union
21+
from typing import Optional, Union
2222

2323
from ardupilot_methodic_configurator import _, __version__
2424
from ardupilot_methodic_configurator.backend_filesystem import LocalFilesystem
@@ -94,20 +94,6 @@ def set_mcu_series(self, mcu: str) -> None:
9494
if mcu.upper() in ("STM32F4XX", "STM32F7XX", "STM32H7XX"):
9595
self.data_model.schema.modify_schema_for_mcu_series(is_optional=True)
9696

97-
def set_vehicle_configuration_template(self, configuration_template: str) -> None:
98-
"""Set the configuration template name in the data."""
99-
self.data_model.set_configuration_template(configuration_template)
100-
101-
def set_values_from_fc_parameters(self, fc_parameters: dict, doc: dict) -> None:
102-
"""
103-
Process flight controller parameters and update the data model.
104-
105-
This delegates to the data model's process_fc_parameters method to handle
106-
all the business logic of processing parameters.
107-
"""
108-
# Delegate to the data model for parameter processing
109-
self.data_model.process_fc_parameters(fc_parameters, doc)
110-
11197
def update_component_protocol_combobox_entries(self, component_path: ComponentPath, connection_type: str) -> str:
11298
"""Updates the Protocol combobox entries based on the selected component connection Type."""
11399
self.data_model.set_component_value(component_path, connection_type)
@@ -183,12 +169,15 @@ def add_entry_or_combobox(
183169
# Determine foreground color based on is_optional flag
184170
fg_color = "gray" if is_optional else "black"
185171

172+
def on_validate_combobox(event: tk.Event) -> bool:
173+
return self._validate_combobox(event, path)
174+
186175
if combobox_values:
187176
cb = ttk.Combobox(entry_frame, values=combobox_values, foreground=fg_color)
188-
cb.bind("<FocusOut>", lambda event, path=path: self._validate_combobox(event, path)) # type: ignore[misc]
189-
cb.bind("<KeyRelease>", lambda event, path=path: self._validate_combobox(event, path)) # type: ignore[misc]
190-
cb.bind("<Return>", lambda event, path=path: self._validate_combobox(event, path)) # type: ignore[misc]
191-
cb.bind("<ButtonRelease>", lambda event, path=path: self._validate_combobox(event, path)) # type: ignore[misc]
177+
cb.bind("<FocusOut>", on_validate_combobox)
178+
cb.bind("<KeyRelease>", on_validate_combobox)
179+
cb.bind("<Return>", on_validate_combobox)
180+
cb.bind("<ButtonRelease>", on_validate_combobox)
192181

193182
# Prevent mouse wheel from changing value when dropdown is not open
194183
def handle_mousewheel(_event: tk.Event, widget: tk.Widget = cb) -> Optional[str]:
@@ -237,18 +226,15 @@ def dropdown_closed(_event: tk.Event, widget: tk.Widget = cb) -> None:
237226
return cb
238227

239228
entry = ttk.Entry(entry_frame, foreground=fg_color)
240-
update_if_valid_function = self.get_validate_function(entry, path)
241-
entry.bind("<FocusOut>", update_if_valid_function)
242-
entry.bind("<KeyRelease>", update_if_valid_function)
243-
entry.bind("<Return>", update_if_valid_function)
244-
entry.insert(0, str(value))
245-
return entry
246229

247-
def get_validate_function(self, entry: ttk.Entry, path: ComponentPath) -> Union[Callable[[tk.Event], object], None]:
248-
def validate_limits(event: tk.Event) -> bool:
249-
return self.validate_entry_limits_ui(event, entry, path)
230+
def on_validate_entry_limits_ui(event: tk.Event) -> bool:
231+
return self._validate_entry_limits_ui(event, entry, path)
250232

251-
return validate_limits
233+
entry.bind("<FocusOut>", on_validate_entry_limits_ui)
234+
entry.bind("<KeyRelease>", on_validate_entry_limits_ui)
235+
entry.bind("<Return>", on_validate_entry_limits_ui)
236+
entry.insert(0, str(value))
237+
return entry
252238

253239
def _validate_combobox(self, event: tk.Event, path: ComponentPath) -> bool:
254240
"""Validates the value of a combobox."""
@@ -285,7 +271,7 @@ def _validate_combobox(self, event: tk.Event, path: ComponentPath) -> bool:
285271
combobox.configure(style="comb_input_valid.TCombobox")
286272
return True
287273

288-
def validate_entry_limits_ui(self, event: Union[None, tk.Event], entry: ttk.Entry, path: ComponentPath) -> bool:
274+
def _validate_entry_limits_ui(self, event: Union[None, tk.Event], entry: ttk.Entry, path: ComponentPath) -> bool:
289275
"""UI wrapper for entry limits validation."""
290276
is_focusout_event = event and event.type in {
291277
tk.EventType.FocusOut,

ardupilot_methodic_configurator/frontend_tkinter_component_editor_base.py

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -318,6 +318,20 @@ def _set_component_value(self, path: ComponentPath, value: str) -> None:
318318
"""Set component value in data model."""
319319
self.data_model.set_component_value(path, value)
320320

321+
def set_vehicle_configuration_template(self, configuration_template: str) -> None:
322+
"""Set the configuration template name in the data."""
323+
self.data_model.set_configuration_template(configuration_template)
324+
325+
def set_values_from_fc_parameters(self, fc_parameters: dict, doc: dict) -> None:
326+
"""
327+
Process flight controller parameters and update the data model.
328+
329+
This delegates to the data model's process_fc_parameters method to handle
330+
all the business logic of processing parameters.
331+
"""
332+
# Delegate to the data model for parameter processing
333+
self.data_model.process_fc_parameters(fc_parameters, doc)
334+
321335
def _update_widget_value(self, path: ComponentPath, value: str) -> None:
322336
"""Update widget value in UI."""
323337
if path in self.entry_widgets:

tests/test_frontend_tkinter_component_editor.py

Lines changed: 22 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -352,14 +352,13 @@ def test_add_entry_or_combobox_optional_field(self, editor_with_mocked_root: Com
352352
mock_entry = MagicMock()
353353
mock_entry_class.return_value = mock_entry
354354

355-
with patch.object(editor_with_mocked_root, "get_validate_function", return_value=MagicMock()):
356-
result = editor_with_mocked_root.add_entry_or_combobox(value, mock_entry_frame, path, is_optional=True)
355+
result = editor_with_mocked_root.add_entry_or_combobox(value, mock_entry_frame, path, is_optional=True)
357356

358-
# Should create entry with gray foreground for optional field
359-
call_args = mock_entry_class.call_args[1]
360-
assert call_args["foreground"] == "gray"
361-
mock_entry.insert.assert_called_once_with(0, str(value))
362-
assert result == mock_entry
357+
# Should create entry with gray foreground for optional field
358+
call_args = mock_entry_class.call_args[1]
359+
assert call_args["foreground"] == "gray"
360+
mock_entry.insert.assert_called_once_with(0, str(value))
361+
assert result == mock_entry
363362

364363
def test_add_entry_or_combobox_fc_connection_type(self, editor_with_mocked_root: ComponentEditorWindow) -> None:
365364
"""Test adding combobox for FC connection type with special binding."""
@@ -401,21 +400,24 @@ def test_add_entry_or_combobox_battery_chemistry(self, editor_with_mocked_root:
401400
bound_events = [call[0][0] for call in bind_calls] # Extract just the event names
402401
assert "<<ComboboxSelected>>" in bound_events
403402

404-
def test_get_validate_function(self, editor_with_mocked_root: ComponentEditorWindow) -> None:
405-
"""Test getting validation function for entry."""
403+
def test_validate_entry_limits_ui_private_method(self, editor_with_mocked_root: ComponentEditorWindow) -> None:
404+
"""Test the private validation method for entry limits."""
405+
mock_event = MagicMock()
406+
mock_event.type = tk.EventType.FocusOut
406407
mock_entry = MagicMock()
408+
mock_entry.get.return_value = "1000"
407409
path = ("Motor", "Specifications", "KV") # Use proper 3-level path
408410

409-
validate_func = editor_with_mocked_root.get_validate_function(mock_entry, path)
411+
editor_with_mocked_root.data_model.validate_entry_limits = MagicMock(return_value=("", None))
412+
editor_with_mocked_root.data_model.set_component_value = MagicMock()
410413

411-
assert callable(validate_func)
414+
result = editor_with_mocked_root._validate_entry_limits_ui(mock_event, mock_entry, path)
412415

413-
# Test the returned function
414-
mock_event = MagicMock()
415-
with patch.object(editor_with_mocked_root, "validate_entry_limits_ui", return_value=True) as mock_validate:
416-
result = validate_func(mock_event)
417-
mock_validate.assert_called_once_with(mock_event, mock_entry, path)
418-
assert result is True
416+
# Should validate and update data model
417+
editor_with_mocked_root.data_model.validate_entry_limits.assert_called_once_with("1000", path)
418+
editor_with_mocked_root.data_model.set_component_value.assert_called_once_with(path, "1000")
419+
mock_entry.configure.assert_called_once_with(style="entry_input_valid.TEntry")
420+
assert result is True
419421

420422
def test_validate_combobox_valid_value(self, editor_with_mocked_root: ComponentEditorWindow) -> None:
421423
"""Test combobox validation with valid value."""
@@ -487,7 +489,7 @@ def test_validate_entry_limits_ui_valid(self, editor_with_mocked_root: Component
487489
editor_with_mocked_root.data_model.validate_entry_limits = MagicMock(return_value=("", None))
488490
editor_with_mocked_root.data_model.set_component_value = MagicMock()
489491

490-
result = editor_with_mocked_root.validate_entry_limits_ui(mock_event, mock_entry, path)
492+
result = editor_with_mocked_root._validate_entry_limits_ui(mock_event, mock_entry, path)
491493

492494
editor_with_mocked_root.data_model.validate_entry_limits.assert_called_once_with("1000", path)
493495
editor_with_mocked_root.data_model.set_component_value.assert_called_once_with(path, "1000")
@@ -505,7 +507,7 @@ def test_validate_entry_limits_ui_invalid_with_correction(self, editor_with_mock
505507
editor_with_mocked_root.data_model.validate_entry_limits = MagicMock(return_value=("Value too high", "8000"))
506508

507509
with patch("ardupilot_methodic_configurator.frontend_tkinter_component_editor.show_error_message") as mock_error:
508-
result = editor_with_mocked_root.validate_entry_limits_ui(mock_event, mock_entry, path)
510+
result = editor_with_mocked_root._validate_entry_limits_ui(mock_event, mock_entry, path)
509511

510512
# Should correct the value and show error
511513
mock_entry.delete.assert_called_once_with(0, tk.END)
@@ -527,7 +529,7 @@ def test_validate_entry_limits_ui_voltage_path(self, editor_with_mocked_root: Co
527529
editor_with_mocked_root.data_model.validate_entry_limits = MagicMock(return_value=("", None))
528530
editor_with_mocked_root.data_model.set_component_value = MagicMock()
529531

530-
result = editor_with_mocked_root.validate_entry_limits_ui(mock_event, mock_entry, path)
532+
result = editor_with_mocked_root._validate_entry_limits_ui(mock_event, mock_entry, path)
531533

532534
editor_with_mocked_root.data_model.validate_entry_limits.assert_called_once_with("4.2", path)
533535
assert result is True

0 commit comments

Comments
 (0)