From d4afcb68324f63ac8aea7cc3b2c82e79d2e643ca Mon Sep 17 00:00:00 2001 From: appel_c Date: Mon, 16 Mar 2026 16:20:30 +0100 Subject: [PATCH] refactor(fuzzy-search): unify is_match for fuzzy search --- bec_widgets/utils/fuzzy_search.py | 35 +++++++++++++++++++ .../components/device_table/device_table.py | 32 ++--------------- .../experiment_selection.py | 27 +------------- tests/unit_tests/test_atlas_admin_view.py | 2 +- 4 files changed, 39 insertions(+), 57 deletions(-) create mode 100644 bec_widgets/utils/fuzzy_search.py diff --git a/bec_widgets/utils/fuzzy_search.py b/bec_widgets/utils/fuzzy_search.py new file mode 100644 index 00000000..24b7c104 --- /dev/null +++ b/bec_widgets/utils/fuzzy_search.py @@ -0,0 +1,35 @@ +"""Module providing fuzzy search utilities for the BEC widgets.""" + +from __future__ import annotations + +from typing import Any + +from thefuzz import fuzz + +FUZZY_SEARCH_THRESHOLD = 80 + + +def is_match( + text: str, row_data: dict[str, Any], relevant_keys: list[str], enable_fuzzy: bool +) -> bool: + """ + Check if the text matches any of the relevant keys in the row data. + + Args: + text (str): The text to search for. + row_data (dict[str, Any]): The row data to search in. + relevant_keys (list[str]): The keys to consider for searching. + enable_fuzzy (bool): Whether to use fuzzy matching. + Returns: + bool: True if a match is found, False otherwise. + """ + for key in relevant_keys: + data = str(row_data.get(key, "") or "") + if enable_fuzzy: + match_ratio = fuzz.partial_ratio(text.lower(), data.lower()) + if match_ratio >= FUZZY_SEARCH_THRESHOLD: + return True + else: + if text.lower() in data.lower(): + return True + return False diff --git a/bec_widgets/widgets/control/device_manager/components/device_table/device_table.py b/bec_widgets/widgets/control/device_manager/components/device_table/device_table.py index a7b716cf..4b4a408c 100644 --- a/bec_widgets/widgets/control/device_manager/components/device_table/device_table.py +++ b/bec_widgets/widgets/control/device_manager/components/device_table/device_table.py @@ -5,9 +5,8 @@ in DeviceTableRow entries. from __future__ import annotations -import traceback from copy import deepcopy -from typing import TYPE_CHECKING, Any, Callable, Iterable, Literal, Tuple +from typing import TYPE_CHECKING, Any, Callable, Iterable, Tuple from bec_lib.atlas_models import Device as DeviceModel from bec_lib.callback_handler import EventType @@ -19,6 +18,7 @@ from thefuzz import fuzz from bec_widgets.utils.bec_widget import BECWidget from bec_widgets.utils.colors import get_accent_colors from bec_widgets.utils.error_popups import SafeSlot +from bec_widgets.utils.fuzzy_search import is_match from bec_widgets.widgets.control.device_manager.components.device_table.device_table_row import ( DeviceTableRow, ) @@ -37,34 +37,6 @@ _DeviceCfgIter = Iterable[dict[str, Any]] # DeviceValidationResult: device_config, config_status, connection_status, error_message _ValidationResultIter = Iterable[Tuple[dict[str, Any], ConfigStatus, ConnectionStatus, str]] -FUZZY_SEARCH_THRESHOLD = 80 - - -def is_match( - text: str, row_data: dict[str, Any], relevant_keys: list[str], enable_fuzzy: bool -) -> bool: - """ - Check if the text matches any of the relevant keys in the row data. - - Args: - text (str): The text to search for. - row_data (dict[str, Any]): The row data to search in. - relevant_keys (list[str]): The keys to consider for searching. - enable_fuzzy (bool): Whether to use fuzzy matching. - Returns: - bool: True if a match is found, False otherwise. - """ - for key in relevant_keys: - data = str(row_data.get(key, "") or "") - if enable_fuzzy: - match_ratio = fuzz.partial_ratio(text.lower(), data.lower()) - if match_ratio >= FUZZY_SEARCH_THRESHOLD: - return True - else: - if text.lower() in data.lower(): - return True - return False - class TableSortOnHold: """Context manager for putting table sorting on hold. Works with nested calls.""" diff --git a/bec_widgets/widgets/services/bec_atlas_admin_view/experiment_selection/experiment_selection.py b/bec_widgets/widgets/services/bec_atlas_admin_view/experiment_selection/experiment_selection.py index f605ed1b..568f48d1 100644 --- a/bec_widgets/widgets/services/bec_atlas_admin_view/experiment_selection/experiment_selection.py +++ b/bec_widgets/widgets/services/bec_atlas_admin_view/experiment_selection/experiment_selection.py @@ -21,6 +21,7 @@ from qtpy.QtWidgets import ( from thefuzz import fuzz from bec_widgets.utils.error_popups import SafeSlot +from bec_widgets.utils.fuzzy_search import is_match from bec_widgets.widgets.services.bec_atlas_admin_view.experiment_selection.experiment_mat_card import ( ExperimentMatCard, ) @@ -31,32 +32,6 @@ from bec_widgets.widgets.services.bec_atlas_admin_view.experiment_selection.util logger = bec_logger.logger -FUZZY_SEARCH_THRESHOLD = 80 - - -def is_match(text: str, data: dict[str, Any], relevant_keys: list[str], enable_fuzzy: bool) -> bool: - """ - Check if the text matches any of the relevant keys in the row data. - - Args: - text (str): The text to search for. - data (dict[str, Any]): The data to search in. - relevant_keys (list[str]): The keys to consider for searching. - enable_fuzzy (bool): Whether to use fuzzy matching. - Returns: - bool: True if a match is found, False otherwise. - """ - for key in relevant_keys: - data_value = str(data.get(key, "") or "") - if enable_fuzzy: - match_ratio = fuzz.partial_ratio(text.lower(), data_value.lower()) - if match_ratio >= FUZZY_SEARCH_THRESHOLD: - return True - else: - if text.lower() in data_value.lower(): - return True - return False - class ExperimentSelection(QWidget): experiment_selected = Signal(dict) diff --git a/tests/unit_tests/test_atlas_admin_view.py b/tests/unit_tests/test_atlas_admin_view.py index 42228d74..80ce88c4 100644 --- a/tests/unit_tests/test_atlas_admin_view.py +++ b/tests/unit_tests/test_atlas_admin_view.py @@ -15,6 +15,7 @@ from bec_lib.messages import ( from qtpy.QtCore import QByteArray, QUrl from qtpy.QtNetwork import QNetworkRequest +from bec_widgets.utils.fuzzy_search import is_match from bec_widgets.widgets.services.bec_atlas_admin_view.bec_atlas_admin_view import BECAtlasAdminView from bec_widgets.widgets.services.bec_atlas_admin_view.bec_atlas_http_service import ( AtlasEndpoints, @@ -28,7 +29,6 @@ from bec_widgets.widgets.services.bec_atlas_admin_view.experiment_selection.expe ) from bec_widgets.widgets.services.bec_atlas_admin_view.experiment_selection.experiment_selection import ( ExperimentSelection, - is_match, ) from bec_widgets.widgets.services.bec_atlas_admin_view.experiment_selection.utils import ( format_datetime,