From b63d39da3c4831897384dc19cc43524a2eab1ed8 Mon Sep 17 00:00:00 2001 From: DebjyotiRay Date: Wed, 4 Jun 2025 08:44:29 +0530 Subject: [PATCH 1/5] Added preprocessing logic; because the 'all_objects' function currently handles 'filter_tags' as a dict --- skbase/lookup/_lookup.py | 57 +++++++----- skbase/lookup/tests/test_lookup.py | 139 +++++++++++++++++++++++++---- 2 files changed, 155 insertions(+), 41 deletions(-) diff --git a/skbase/lookup/_lookup.py b/skbase/lookup/_lookup.py index be274d27..7402df09 100644 --- a/skbase/lookup/_lookup.py +++ b/skbase/lookup/_lookup.py @@ -190,34 +190,21 @@ def _filter_by_tags(obj, tag_filter=None, as_dataframe=True): if tag_filter is None: return True - type_msg = ( - "filter_tags argument of all_objects must be " - "a dict with str or re.Pattern keys, " - "str, or iterable of str, " - "but found" - ) - - if not isinstance(tag_filter, (str, Iterable, dict)): - raise TypeError(f"{type_msg} type {type(tag_filter)}") + if not isinstance(tag_filter, dict): + raise TypeError( + "tag_filter argument must be a dict with str keys, " + f"but found type {type(tag_filter)}" + ) if not hasattr(obj, "get_class_tag"): return False - # case: tag_filter is string - if isinstance(tag_filter, str): - tag_filter = {tag_filter: True} - - # case: tag_filter is iterable of str but not dict - # If a iterable of strings is provided, check that all are in the returned tag_dict - if isinstance(tag_filter, Iterable) and not isinstance(tag_filter, dict): - if not all(isinstance(t, str) for t in tag_filter): - raise ValueError(f"{type_msg} {tag_filter}") - tag_filter = dict.fromkeys(tag_filter, True) - - # case: tag_filter is dict # check that all keys are str if not all(isinstance(t, str) for t in tag_filter.keys()): - raise ValueError(f"{type_msg} {tag_filter}") + raise ValueError( + "tag_filter argument must be a dict with str keys, " + f"but found keys: {tag_filter.keys()}" + ) cond_sat = True @@ -625,6 +612,19 @@ def get_package_metadata( - "contains_base_objects": whether any module classes that inherit from ``BaseObject``. """ + # Handle tag_filter conversion from str or list of str to dict + if tag_filter is not None: + if isinstance(tag_filter, str): + tag_filter = {tag_filter: True} + elif isinstance(tag_filter, (list, tuple)) and all( + isinstance(tag, str) for tag in tag_filter + ): + tag_filter = dict.fromkeys(tag_filter, True) + elif not isinstance(tag_filter, dict): + raise TypeError("tag_filter must be a str, list of str, or dict") + else: + tag_filter = tag_filter.copy() + module, path, loader = _determine_module_path(package_name, path) module_info: MutableMapping = {} # of ModuleInfo type # Get any metadata at the top-level of the provided package @@ -844,6 +844,19 @@ class name if ``return_names=False`` and ``return_tags is not None``. Modified version of ``scikit-learn``'s and sktime's ``all_estimators`` to allow users to find ``BaseObject`` descendants in ``skbase`` and other packages. """ + # Handle filter_tags conversion from str or list of str to dict + if filter_tags is not None: + if isinstance(filter_tags, str): + filter_tags = {filter_tags: True} + elif isinstance(filter_tags, (list, tuple)) and all( + isinstance(tag, str) for tag in filter_tags + ): + filter_tags = dict.fromkeys(filter_tags, True) + elif not isinstance(filter_tags, dict): + raise TypeError("filter_tags must be a str, list of str, or dict") + else: + filter_tags = filter_tags.copy() + _, root, _ = _determine_module_path(package_name, path) modules_to_ignore = _coerce_to_tuple(modules_to_ignore) exclude_objects = _coerce_to_tuple(exclude_objects) diff --git a/skbase/lookup/tests/test_lookup.py b/skbase/lookup/tests/test_lookup.py index 8bec321b..d2ae0a9c 100644 --- a/skbase/lookup/tests/test_lookup.py +++ b/skbase/lookup/tests/test_lookup.py @@ -374,19 +374,13 @@ def test_filter_by_tags(): # Even if the class isn't a BaseObject assert _filter_by_tags(NotABaseObject) is True - # Check when tag_filter is a str and present in the class - assert _filter_by_tags(ClassWithABTrue, tag_filter="A") is True - # Check when tag_filter is str and not present in the class - assert _filter_by_tags(Parent, tag_filter="A") is False + # Check when tag_filter is a dict with single tag present in the class + assert _filter_by_tags(ClassWithABTrue, tag_filter={"A": True}) is True + # Check when tag_filter is dict with tag not present in the class + assert _filter_by_tags(Parent, tag_filter={"A": True}) is False # Test functionality when tag present and object doesn't have tag interface - assert _filter_by_tags(NotABaseObject, tag_filter="A") is False - - # Test functionality where tag_filter is Iterable of str - # all tags in iterable are in the class - assert _filter_by_tags(ClassWithABTrue, ("A", "B")) is True - # Some tags in iterable are in class and others aren't - assert _filter_by_tags(ClassWithABTrue, ("A", "B", "C", "D", "E")) is False + assert _filter_by_tags(NotABaseObject, tag_filter={"A": True}) is False # Test functionality where tag_filter is Dict[str, Any] # All keys in dict are in tag_filter and values all match @@ -396,17 +390,21 @@ def test_filter_by_tags(): # At least 1 key in dict is not in tag_filter assert _filter_by_tags(Parent, {"E": 1, "B": 2}) is False - # Iterable tags should be all strings - with pytest.raises(ValueError, match=r"filter_tags"): - assert _filter_by_tags(Parent, ("A", "B", 3)) + # Tags that aren't dict should raise TypeError + with pytest.raises(TypeError, match=r"tag_filter argument must be a dict"): + _filter_by_tags(Parent, "A") + + with pytest.raises(TypeError, match=r"tag_filter argument must be a dict"): + _filter_by_tags(Parent, ["A", "B"]) - # Tags that aren't iterable have to be strings - with pytest.raises(TypeError, match=r"filter_tags"): - assert _filter_by_tags(Parent, 7.0) + with pytest.raises(TypeError, match=r"tag_filter argument must be a dict"): + _filter_by_tags(Parent, 7.0) # Dictionary tags should have string keys - with pytest.raises(ValueError, match=r"filter_tags"): - assert _filter_by_tags(Parent, {7: 11}) + with pytest.raises( + ValueError, match=r"tag_filter argument must be a dict with str keys" + ): + _filter_by_tags(Parent, {7: 11}) def test_walk_returns_expected_format(fixture_skbase_root_path): @@ -998,6 +996,109 @@ def test_all_object_tag_filter(tag_filter): assert len(unfiltered_classes) > len(filtered_classes) +def test_all_objects_filter_tags_preprocessing(): + """Test filter_tags preprocessing in all_objects function.""" + # Test string input conversion + objs_str = all_objects( + package_name="skbase", + return_names=True, + as_dataframe=True, + filter_tags="A", + ) + + objs_dict = all_objects( + package_name="skbase", + return_names=True, + as_dataframe=True, + filter_tags={"A": True}, + ) + + # Results should be identical + assert objs_str.equals( + objs_dict + ), "String and dict filter should return same results" + + # Test list of strings input conversion + objs_list = all_objects( + package_name="skbase", + return_names=True, + as_dataframe=True, + filter_tags=["A", "B"], + ) + + objs_dict_multi = all_objects( + package_name="skbase", + return_names=True, + as_dataframe=True, + filter_tags={"A": True, "B": True}, + ) + + # Results should be identical + assert objs_list.equals( + objs_dict_multi + ), "List and dict filter should return same results" + + +@pytest.mark.parametrize( + "invalid_filter", + [ + 123, # int + 12.5, # float + object(), # object + ["A", 123], # list with non-string + ("A", 123), # tuple with non-string + ], +) +def test_all_objects_filter_tags_invalid_types(invalid_filter): + """Test that invalid filter_tags types raise TypeError.""" + with pytest.raises( + TypeError, match="filter_tags must be a str, list of str, or dict" + ): + all_objects( + package_name="skbase", + filter_tags=invalid_filter, + ) + + +def test_all_objects_filter_tags_empty_list(): + """Test that empty list filter_tags works correctly.""" + objs_empty_list = all_objects( + package_name="skbase", + return_names=True, + as_dataframe=True, + filter_tags=[], + ) + + objs_empty_dict = all_objects( + package_name="skbase", + return_names=True, + as_dataframe=True, + filter_tags={}, + ) + + # Results should be identical + assert objs_empty_list.equals( + objs_empty_dict + ), "Empty list and empty dict should return same results" + + +def test_all_objects_filter_tags_copy_behavior(): + """Test that filter_tags dict is copied and not modified in place.""" + original_filter = {"A": "1"} + original_copy = original_filter.copy() + + # Call all_objects with the filter + all_objects( + package_name="skbase", + filter_tags=original_filter, + ) + + # Original dict should be unchanged + assert ( + original_filter == original_copy + ), "Original filter_tags dict should not be modified" + + def test_all_object_tag_filter_regex(): """Test all_objects filters by tag as expected, when using regex.""" import re From bf8676d6a047a2480bf382b6f7d0771de2627c74 Mon Sep 17 00:00:00 2001 From: DebjyotiRay Date: Thu, 5 Jun 2025 23:08:06 +0530 Subject: [PATCH 2/5] modifying changes to keep the initial logic intact --- skbase/lookup/_lookup.py | 22 +- skbase/lookup/tests/test_lookup.py | 313 +++++++++++++++++++---------- 2 files changed, 216 insertions(+), 119 deletions(-) diff --git a/skbase/lookup/_lookup.py b/skbase/lookup/_lookup.py index 7402df09..81cff01c 100644 --- a/skbase/lookup/_lookup.py +++ b/skbase/lookup/_lookup.py @@ -171,7 +171,7 @@ def _filter_by_tags(obj, tag_filter=None, as_dataframe=True): Parameters ---------- obj : BaseObject, an sktime estimator - tag_filter : dict of (str or list of str), default=None + tag_filter : str, list[str] or dict of (str or list of str), default=None subsets the returned estimators as follows: each key/value pair is statement in "and"/conjunction @@ -190,21 +190,23 @@ def _filter_by_tags(obj, tag_filter=None, as_dataframe=True): if tag_filter is None: return True - if not isinstance(tag_filter, dict): - raise TypeError( - "tag_filter argument must be a dict with str keys, " - f"but found type {type(tag_filter)}" - ) + # Handle backward compatibility - convert str/list/tuple to dict + if isinstance(tag_filter, str): + tag_filter = {tag_filter: True} + elif isinstance(tag_filter, (list, tuple)): + # Check if all elements are strings (original error handling) + if not all(isinstance(tag, str) for tag in tag_filter): + raise ValueError("filter_tags") + tag_filter = dict.fromkeys(tag_filter, True) + elif not isinstance(tag_filter, dict): + raise TypeError("filter_tags") if not hasattr(obj, "get_class_tag"): return False # check that all keys are str if not all(isinstance(t, str) for t in tag_filter.keys()): - raise ValueError( - "tag_filter argument must be a dict with str keys, " - f"but found keys: {tag_filter.keys()}" - ) + raise ValueError("filter_tags") cond_sat = True diff --git a/skbase/lookup/tests/test_lookup.py b/skbase/lookup/tests/test_lookup.py index d2ae0a9c..0815ba81 100644 --- a/skbase/lookup/tests/test_lookup.py +++ b/skbase/lookup/tests/test_lookup.py @@ -374,13 +374,19 @@ def test_filter_by_tags(): # Even if the class isn't a BaseObject assert _filter_by_tags(NotABaseObject) is True - # Check when tag_filter is a dict with single tag present in the class - assert _filter_by_tags(ClassWithABTrue, tag_filter={"A": True}) is True - # Check when tag_filter is dict with tag not present in the class - assert _filter_by_tags(Parent, tag_filter={"A": True}) is False + # Check when tag_filter is a str and present in the class + assert _filter_by_tags(ClassWithABTrue, tag_filter="A") is True + # Check when tag_filter is str and not present in the class + assert _filter_by_tags(Parent, tag_filter="A") is False # Test functionality when tag present and object doesn't have tag interface - assert _filter_by_tags(NotABaseObject, tag_filter={"A": True}) is False + assert _filter_by_tags(NotABaseObject, tag_filter="A") is False + + # Test functionality where tag_filter is Iterable of str + # all tags in iterable are in the class + assert _filter_by_tags(ClassWithABTrue, ("A", "B")) is True + # Some tags in iterable are in class and others aren't + assert _filter_by_tags(ClassWithABTrue, ("A", "B", "C", "D", "E")) is False # Test functionality where tag_filter is Dict[str, Any] # All keys in dict are in tag_filter and values all match @@ -390,21 +396,17 @@ def test_filter_by_tags(): # At least 1 key in dict is not in tag_filter assert _filter_by_tags(Parent, {"E": 1, "B": 2}) is False - # Tags that aren't dict should raise TypeError - with pytest.raises(TypeError, match=r"tag_filter argument must be a dict"): - _filter_by_tags(Parent, "A") - - with pytest.raises(TypeError, match=r"tag_filter argument must be a dict"): - _filter_by_tags(Parent, ["A", "B"]) + # Iterable tags should be all strings + with pytest.raises(ValueError, match=r"filter_tags"): + assert _filter_by_tags(Parent, ("A", "B", 3)) - with pytest.raises(TypeError, match=r"tag_filter argument must be a dict"): - _filter_by_tags(Parent, 7.0) + # Tags that aren't iterable have to be strings + with pytest.raises(TypeError, match=r"filter_tags"): + assert _filter_by_tags(Parent, 7.0) # Dictionary tags should have string keys - with pytest.raises( - ValueError, match=r"tag_filter argument must be a dict with str keys" - ): - _filter_by_tags(Parent, {7: 11}) + with pytest.raises(ValueError, match=r"filter_tags"): + assert _filter_by_tags(Parent, {7: 11}) def test_walk_returns_expected_format(fixture_skbase_root_path): @@ -996,8 +998,86 @@ def test_all_object_tag_filter(tag_filter): assert len(unfiltered_classes) > len(filtered_classes) -def test_all_objects_filter_tags_preprocessing(): - """Test filter_tags preprocessing in all_objects function.""" +def test_all_object_tag_filter_regex(): + """Test all_objects filters by tag as expected, when using regex.""" + import re + + # search for class where "A" has at least one 1, and "C" has "23" in the tag value + # this sohuld find Parent but not Child + filter_tags = {"A": re.compile(r"^(?=.*1).*$"), "C": re.compile(r".+23.+")} + + # Results applying filter + objs = all_objects( + package_name="skbase", + return_names=True, + as_dataframe=True, + return_tags=None, + filter_tags=filter_tags, + ) + filtered_classes = objs.iloc[:, 1].tolist() + # Verify filtered results have right output type + _check_all_object_output_types( + objs, as_dataframe=True, return_names=True, return_tags=None + ) + + # Results without filter + objs = all_objects( + package_name="skbase", + return_names=True, + as_dataframe=True, + return_tags=None, + ) + unfiltered_classes = objs.iloc[:, 1].tolist() + + # as stated above, we should find only Parent (and not Child) + assert len(unfiltered_classes) > len(filtered_classes) + names = [kls.__name__ for kls in filtered_classes] + assert "Parent" in names + + +@pytest.mark.parametrize("class_lookup", [{"base_object": BaseObject}]) +@pytest.mark.parametrize("class_filter", [None, "base_object"]) +def test_all_object_class_lookup(class_lookup, class_filter): + """Test all_objects class_lookup parameter works as expected..""" + # Results applying filter + objs = all_objects( + package_name="skbase", + return_names=True, + as_dataframe=True, + return_tags=None, + object_types=class_filter, + class_lookup=class_lookup, + ) + # filtered_classes = objs.iloc[:, 1].tolist() + # Verify filtered results have right output type + _check_all_object_output_types( + objs, as_dataframe=True, return_names=True, return_tags=None + ) + + +@pytest.mark.parametrize("class_lookup", [None, {"base_object": BaseObject}]) +@pytest.mark.parametrize("class_filter", ["invalid_alias", 7]) +def test_all_object_class_lookup_invalid_object_types_raises( + class_lookup, class_filter +): + """Test all_objects use of object filtering raises errors as expected.""" + # Results applying filter + with pytest.raises(ValueError): + all_objects( + package_name="skbase", + return_names=True, + as_dataframe=True, + return_tags=None, + object_types=class_filter, + class_lookup=class_lookup, + ) + +# ============================================================================== +# NEW TESTS FOR FILTER_TAGS PREPROCESSING FUNCTIONALITY +# ============================================================================== + +def test_all_objects_filter_tags_string_preprocessing(): + """Test all_objects converts string filter_tags to dict correctly.""" # Test string input conversion objs_str = all_objects( package_name="skbase", @@ -1014,10 +1094,11 @@ def test_all_objects_filter_tags_preprocessing(): ) # Results should be identical - assert objs_str.equals( - objs_dict - ), "String and dict filter should return same results" + assert objs_str.equals(objs_dict), "String and dict filter should return same results" + +def test_all_objects_filter_tags_list_preprocessing(): + """Test all_objects converts list filter_tags to dict correctly.""" # Test list of strings input conversion objs_list = all_objects( package_name="skbase", @@ -1026,7 +1107,7 @@ def test_all_objects_filter_tags_preprocessing(): filter_tags=["A", "B"], ) - objs_dict_multi = all_objects( + objs_dict = all_objects( package_name="skbase", return_names=True, as_dataframe=True, @@ -1034,9 +1115,68 @@ def test_all_objects_filter_tags_preprocessing(): ) # Results should be identical - assert objs_list.equals( - objs_dict_multi - ), "List and dict filter should return same results" + assert objs_list.equals(objs_dict), "List and dict filter should return same results" + + +def test_all_objects_filter_tags_tuple_preprocessing(): + """Test all_objects converts tuple filter_tags to dict correctly.""" + # Test tuple of strings input conversion + objs_tuple = all_objects( + package_name="skbase", + return_names=True, + as_dataframe=True, + filter_tags=("A", "B"), + ) + + objs_dict = all_objects( + package_name="skbase", + return_names=True, + as_dataframe=True, + filter_tags={"A": True, "B": True}, + ) + + # Results should be identical + assert objs_tuple.equals(objs_dict), "Tuple and dict filter should return same results" + + +def test_get_package_metadata_filter_tags_string_preprocessing(): + """Test get_package_metadata converts string tag_filter to dict correctly.""" + result_str = get_package_metadata( + "skbase", + modules_to_ignore="skbase", + tag_filter="A", + classes_to_exclude=TagAliaserMixin, + ) + + result_dict = get_package_metadata( + "skbase", + modules_to_ignore="skbase", + tag_filter={"A": True}, + classes_to_exclude=TagAliaserMixin, + ) + + # Results should be identical + assert result_str.keys() == result_dict.keys() + + +def test_get_package_metadata_filter_tags_list_preprocessing(): + """Test get_package_metadata converts list tag_filter to dict correctly.""" + result_list = get_package_metadata( + "skbase", + modules_to_ignore="skbase", + tag_filter=["A", "B"], + classes_to_exclude=TagAliaserMixin, + ) + + result_dict = get_package_metadata( + "skbase", + modules_to_ignore="skbase", + tag_filter={"A": True, "B": True}, + classes_to_exclude=TagAliaserMixin, + ) + + # Results should be identical + assert result_list.keys() == result_dict.keys() @pytest.mark.parametrize( @@ -1049,19 +1189,36 @@ def test_all_objects_filter_tags_preprocessing(): ("A", 123), # tuple with non-string ], ) -def test_all_objects_filter_tags_invalid_types(invalid_filter): - """Test that invalid filter_tags types raise TypeError.""" - with pytest.raises( - TypeError, match="filter_tags must be a str, list of str, or dict" - ): +def test_all_objects_filter_tags_invalid_types_preprocessing(invalid_filter): + """Test that invalid filter_tags types raise TypeError in all_objects.""" + with pytest.raises(TypeError, match="filter_tags must be a str, list of str, or dict"): all_objects( package_name="skbase", filter_tags=invalid_filter, ) -def test_all_objects_filter_tags_empty_list(): - """Test that empty list filter_tags works correctly.""" +@pytest.mark.parametrize( + "invalid_filter", + [ + 123, # int + 12.5, # float + object(), # object + ["A", 123], # list with non-string + ("A", 123), # tuple with non-string + ], +) +def test_get_package_metadata_filter_tags_invalid_types_preprocessing(invalid_filter): + """Test that invalid tag_filter types raise TypeError in get_package_metadata.""" + with pytest.raises(TypeError, match="tag_filter must be a str, list of str, or dict"): + get_package_metadata( + "skbase", + tag_filter=invalid_filter, + ) + + +def test_all_objects_filter_tags_empty_list_preprocessing(): + """Test all_objects handles empty list filter_tags correctly.""" objs_empty_list = all_objects( package_name="skbase", return_names=True, @@ -1077,12 +1234,10 @@ def test_all_objects_filter_tags_empty_list(): ) # Results should be identical - assert objs_empty_list.equals( - objs_empty_dict - ), "Empty list and empty dict should return same results" + assert objs_empty_list.equals(objs_empty_dict), "Empty list and empty dict should return same results" -def test_all_objects_filter_tags_copy_behavior(): +def test_all_objects_filter_tags_dict_copy_behavior(): """Test that filter_tags dict is copied and not modified in place.""" original_filter = {"A": "1"} original_copy = original_filter.copy() @@ -1094,81 +1249,21 @@ def test_all_objects_filter_tags_copy_behavior(): ) # Original dict should be unchanged - assert ( - original_filter == original_copy - ), "Original filter_tags dict should not be modified" - - -def test_all_object_tag_filter_regex(): - """Test all_objects filters by tag as expected, when using regex.""" - import re - - # search for class where "A" has at least one 1, and "C" has "23" in the tag value - # this sohuld find Parent but not Child - filter_tags = {"A": re.compile(r"^(?=.*1).*$"), "C": re.compile(r".+23.+")} - - # Results applying filter - objs = all_objects( - package_name="skbase", - return_names=True, - as_dataframe=True, - return_tags=None, - filter_tags=filter_tags, - ) - filtered_classes = objs.iloc[:, 1].tolist() - # Verify filtered results have right output type - _check_all_object_output_types( - objs, as_dataframe=True, return_names=True, return_tags=None - ) + assert original_filter == original_copy, "Original filter_tags dict should not be modified" - # Results without filter - objs = all_objects( - package_name="skbase", - return_names=True, - as_dataframe=True, - return_tags=None, - ) - unfiltered_classes = objs.iloc[:, 1].tolist() - - # as stated above, we should find only Parent (and not Child) - assert len(unfiltered_classes) > len(filtered_classes) - names = [kls.__name__ for kls in filtered_classes] - assert "Parent" in names +def test_get_package_metadata_filter_tags_dict_copy_behavior(): + """Test that tag_filter dict is copied and not modified in place.""" + original_filter = {"A": "1"} + original_copy = original_filter.copy() -@pytest.mark.parametrize("class_lookup", [{"base_object": BaseObject}]) -@pytest.mark.parametrize("class_filter", [None, "base_object"]) -def test_all_object_class_lookup(class_lookup, class_filter): - """Test all_objects class_lookup parameter works as expected..""" - # Results applying filter - objs = all_objects( - package_name="skbase", - return_names=True, - as_dataframe=True, - return_tags=None, - object_types=class_filter, - class_lookup=class_lookup, - ) - # filtered_classes = objs.iloc[:, 1].tolist() - # Verify filtered results have right output type - _check_all_object_output_types( - objs, as_dataframe=True, return_names=True, return_tags=None + # Call get_package_metadata with the filter + get_package_metadata( + "skbase", + tag_filter=original_filter, + classes_to_exclude=TagAliaserMixin, ) + # Original dict should be unchanged + assert original_filter == original_copy, "Original tag_filter dict should not be modified" -@pytest.mark.parametrize("class_lookup", [None, {"base_object": BaseObject}]) -@pytest.mark.parametrize("class_filter", ["invalid_alias", 7]) -def test_all_object_class_lookup_invalid_object_types_raises( - class_lookup, class_filter -): - """Test all_objects use of object filtering raises errors as expected.""" - # Results applying filter - with pytest.raises(ValueError): - all_objects( - package_name="skbase", - return_names=True, - as_dataframe=True, - return_tags=None, - object_types=class_filter, - class_lookup=class_lookup, - ) From 02bc5f79ea97ee1584029829e5b9a5043c90dc53 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Thu, 5 Jun 2025 17:40:43 +0000 Subject: [PATCH 3/5] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- skbase/lookup/tests/test_lookup.py | 47 ++++++++++++++++++++---------- 1 file changed, 32 insertions(+), 15 deletions(-) diff --git a/skbase/lookup/tests/test_lookup.py b/skbase/lookup/tests/test_lookup.py index 0815ba81..57c8049d 100644 --- a/skbase/lookup/tests/test_lookup.py +++ b/skbase/lookup/tests/test_lookup.py @@ -1072,10 +1072,12 @@ def test_all_object_class_lookup_invalid_object_types_raises( class_lookup=class_lookup, ) + # ============================================================================== # NEW TESTS FOR FILTER_TAGS PREPROCESSING FUNCTIONALITY # ============================================================================== + def test_all_objects_filter_tags_string_preprocessing(): """Test all_objects converts string filter_tags to dict correctly.""" # Test string input conversion @@ -1094,7 +1096,9 @@ def test_all_objects_filter_tags_string_preprocessing(): ) # Results should be identical - assert objs_str.equals(objs_dict), "String and dict filter should return same results" + assert objs_str.equals( + objs_dict + ), "String and dict filter should return same results" def test_all_objects_filter_tags_list_preprocessing(): @@ -1115,7 +1119,9 @@ def test_all_objects_filter_tags_list_preprocessing(): ) # Results should be identical - assert objs_list.equals(objs_dict), "List and dict filter should return same results" + assert objs_list.equals( + objs_dict + ), "List and dict filter should return same results" def test_all_objects_filter_tags_tuple_preprocessing(): @@ -1136,7 +1142,9 @@ def test_all_objects_filter_tags_tuple_preprocessing(): ) # Results should be identical - assert objs_tuple.equals(objs_dict), "Tuple and dict filter should return same results" + assert objs_tuple.equals( + objs_dict + ), "Tuple and dict filter should return same results" def test_get_package_metadata_filter_tags_string_preprocessing(): @@ -1147,14 +1155,14 @@ def test_get_package_metadata_filter_tags_string_preprocessing(): tag_filter="A", classes_to_exclude=TagAliaserMixin, ) - + result_dict = get_package_metadata( "skbase", - modules_to_ignore="skbase", + modules_to_ignore="skbase", tag_filter={"A": True}, classes_to_exclude=TagAliaserMixin, ) - + # Results should be identical assert result_str.keys() == result_dict.keys() @@ -1167,14 +1175,14 @@ def test_get_package_metadata_filter_tags_list_preprocessing(): tag_filter=["A", "B"], classes_to_exclude=TagAliaserMixin, ) - + result_dict = get_package_metadata( "skbase", - modules_to_ignore="skbase", + modules_to_ignore="skbase", tag_filter={"A": True, "B": True}, classes_to_exclude=TagAliaserMixin, ) - + # Results should be identical assert result_list.keys() == result_dict.keys() @@ -1191,7 +1199,9 @@ def test_get_package_metadata_filter_tags_list_preprocessing(): ) def test_all_objects_filter_tags_invalid_types_preprocessing(invalid_filter): """Test that invalid filter_tags types raise TypeError in all_objects.""" - with pytest.raises(TypeError, match="filter_tags must be a str, list of str, or dict"): + with pytest.raises( + TypeError, match="filter_tags must be a str, list of str, or dict" + ): all_objects( package_name="skbase", filter_tags=invalid_filter, @@ -1210,7 +1220,9 @@ def test_all_objects_filter_tags_invalid_types_preprocessing(invalid_filter): ) def test_get_package_metadata_filter_tags_invalid_types_preprocessing(invalid_filter): """Test that invalid tag_filter types raise TypeError in get_package_metadata.""" - with pytest.raises(TypeError, match="tag_filter must be a str, list of str, or dict"): + with pytest.raises( + TypeError, match="tag_filter must be a str, list of str, or dict" + ): get_package_metadata( "skbase", tag_filter=invalid_filter, @@ -1234,7 +1246,9 @@ def test_all_objects_filter_tags_empty_list_preprocessing(): ) # Results should be identical - assert objs_empty_list.equals(objs_empty_dict), "Empty list and empty dict should return same results" + assert objs_empty_list.equals( + objs_empty_dict + ), "Empty list and empty dict should return same results" def test_all_objects_filter_tags_dict_copy_behavior(): @@ -1249,7 +1263,9 @@ def test_all_objects_filter_tags_dict_copy_behavior(): ) # Original dict should be unchanged - assert original_filter == original_copy, "Original filter_tags dict should not be modified" + assert ( + original_filter == original_copy + ), "Original filter_tags dict should not be modified" def test_get_package_metadata_filter_tags_dict_copy_behavior(): @@ -1265,5 +1281,6 @@ def test_get_package_metadata_filter_tags_dict_copy_behavior(): ) # Original dict should be unchanged - assert original_filter == original_copy, "Original tag_filter dict should not be modified" - + assert ( + original_filter == original_copy + ), "Original tag_filter dict should not be modified" From 9a8d7e15be23fcff25fa6413dd2e3129e5f105bc Mon Sep 17 00:00:00 2001 From: DebjyotiRay Date: Sun, 8 Jun 2025 01:52:42 +0530 Subject: [PATCH 4/5] updated all changes ; --- skbase/lookup/_lookup.py | 26 ----- skbase/lookup/tests/test_lookup.py | 162 +++++------------------------ 2 files changed, 27 insertions(+), 161 deletions(-) diff --git a/skbase/lookup/_lookup.py b/skbase/lookup/_lookup.py index 81cff01c..b1ca3751 100644 --- a/skbase/lookup/_lookup.py +++ b/skbase/lookup/_lookup.py @@ -614,19 +614,6 @@ def get_package_metadata( - "contains_base_objects": whether any module classes that inherit from ``BaseObject``. """ - # Handle tag_filter conversion from str or list of str to dict - if tag_filter is not None: - if isinstance(tag_filter, str): - tag_filter = {tag_filter: True} - elif isinstance(tag_filter, (list, tuple)) and all( - isinstance(tag, str) for tag in tag_filter - ): - tag_filter = dict.fromkeys(tag_filter, True) - elif not isinstance(tag_filter, dict): - raise TypeError("tag_filter must be a str, list of str, or dict") - else: - tag_filter = tag_filter.copy() - module, path, loader = _determine_module_path(package_name, path) module_info: MutableMapping = {} # of ModuleInfo type # Get any metadata at the top-level of the provided package @@ -846,19 +833,6 @@ class name if ``return_names=False`` and ``return_tags is not None``. Modified version of ``scikit-learn``'s and sktime's ``all_estimators`` to allow users to find ``BaseObject`` descendants in ``skbase`` and other packages. """ - # Handle filter_tags conversion from str or list of str to dict - if filter_tags is not None: - if isinstance(filter_tags, str): - filter_tags = {filter_tags: True} - elif isinstance(filter_tags, (list, tuple)) and all( - isinstance(tag, str) for tag in filter_tags - ): - filter_tags = dict.fromkeys(filter_tags, True) - elif not isinstance(filter_tags, dict): - raise TypeError("filter_tags must be a str, list of str, or dict") - else: - filter_tags = filter_tags.copy() - _, root, _ = _determine_module_path(package_name, path) modules_to_ignore = _coerce_to_tuple(modules_to_ignore) exclude_objects = _coerce_to_tuple(exclude_objects) diff --git a/skbase/lookup/tests/test_lookup.py b/skbase/lookup/tests/test_lookup.py index 0815ba81..3e46cc1b 100644 --- a/skbase/lookup/tests/test_lookup.py +++ b/skbase/lookup/tests/test_lookup.py @@ -1072,112 +1072,11 @@ def test_all_object_class_lookup_invalid_object_types_raises( class_lookup=class_lookup, ) + # ============================================================================== -# NEW TESTS FOR FILTER_TAGS PREPROCESSING FUNCTIONALITY +# ADDITIONAL TESTS FOR EDGE CASES AND ERROR HANDLING # ============================================================================== -def test_all_objects_filter_tags_string_preprocessing(): - """Test all_objects converts string filter_tags to dict correctly.""" - # Test string input conversion - objs_str = all_objects( - package_name="skbase", - return_names=True, - as_dataframe=True, - filter_tags="A", - ) - - objs_dict = all_objects( - package_name="skbase", - return_names=True, - as_dataframe=True, - filter_tags={"A": True}, - ) - - # Results should be identical - assert objs_str.equals(objs_dict), "String and dict filter should return same results" - - -def test_all_objects_filter_tags_list_preprocessing(): - """Test all_objects converts list filter_tags to dict correctly.""" - # Test list of strings input conversion - objs_list = all_objects( - package_name="skbase", - return_names=True, - as_dataframe=True, - filter_tags=["A", "B"], - ) - - objs_dict = all_objects( - package_name="skbase", - return_names=True, - as_dataframe=True, - filter_tags={"A": True, "B": True}, - ) - - # Results should be identical - assert objs_list.equals(objs_dict), "List and dict filter should return same results" - - -def test_all_objects_filter_tags_tuple_preprocessing(): - """Test all_objects converts tuple filter_tags to dict correctly.""" - # Test tuple of strings input conversion - objs_tuple = all_objects( - package_name="skbase", - return_names=True, - as_dataframe=True, - filter_tags=("A", "B"), - ) - - objs_dict = all_objects( - package_name="skbase", - return_names=True, - as_dataframe=True, - filter_tags={"A": True, "B": True}, - ) - - # Results should be identical - assert objs_tuple.equals(objs_dict), "Tuple and dict filter should return same results" - - -def test_get_package_metadata_filter_tags_string_preprocessing(): - """Test get_package_metadata converts string tag_filter to dict correctly.""" - result_str = get_package_metadata( - "skbase", - modules_to_ignore="skbase", - tag_filter="A", - classes_to_exclude=TagAliaserMixin, - ) - - result_dict = get_package_metadata( - "skbase", - modules_to_ignore="skbase", - tag_filter={"A": True}, - classes_to_exclude=TagAliaserMixin, - ) - - # Results should be identical - assert result_str.keys() == result_dict.keys() - - -def test_get_package_metadata_filter_tags_list_preprocessing(): - """Test get_package_metadata converts list tag_filter to dict correctly.""" - result_list = get_package_metadata( - "skbase", - modules_to_ignore="skbase", - tag_filter=["A", "B"], - classes_to_exclude=TagAliaserMixin, - ) - - result_dict = get_package_metadata( - "skbase", - modules_to_ignore="skbase", - tag_filter={"A": True, "B": True}, - classes_to_exclude=TagAliaserMixin, - ) - - # Results should be identical - assert result_list.keys() == result_dict.keys() - @pytest.mark.parametrize( "invalid_filter", @@ -1189,9 +1088,10 @@ def test_get_package_metadata_filter_tags_list_preprocessing(): ("A", 123), # tuple with non-string ], ) -def test_all_objects_filter_tags_invalid_types_preprocessing(invalid_filter): - """Test that invalid filter_tags types raise TypeError in all_objects.""" - with pytest.raises(TypeError, match="filter_tags must be a str, list of str, or dict"): +def test_all_objects_filter_tags_invalid_types(invalid_filter): + """Test that invalid filter_tags types raise appropriate errors in all_objects.""" + # The error is raised by _filter_by_tags, but we test through all_objects + with pytest.raises((TypeError, ValueError)): all_objects( package_name="skbase", filter_tags=invalid_filter, @@ -1208,16 +1108,21 @@ def test_all_objects_filter_tags_invalid_types_preprocessing(invalid_filter): ("A", 123), # tuple with non-string ], ) -def test_get_package_metadata_filter_tags_invalid_types_preprocessing(invalid_filter): - """Test that invalid tag_filter types raise TypeError in get_package_metadata.""" - with pytest.raises(TypeError, match="tag_filter must be a str, list of str, or dict"): +def test_get_package_metadata_filter_tags_invalid_types(invalid_filter): + """Test that invalid tag_filter types raise appropriate errors. + + Tests get_package_metadata function specifically. + """ + # The error is raised by _filter_by_tags, but we test through + # get_package_metadata + with pytest.raises((TypeError, ValueError)): get_package_metadata( "skbase", tag_filter=invalid_filter, ) -def test_all_objects_filter_tags_empty_list_preprocessing(): +def test_all_objects_filter_tags_empty_list(): """Test all_objects handles empty list filter_tags correctly.""" objs_empty_list = all_objects( package_name="skbase", @@ -1234,36 +1139,23 @@ def test_all_objects_filter_tags_empty_list_preprocessing(): ) # Results should be identical - assert objs_empty_list.equals(objs_empty_dict), "Empty list and empty dict should return same results" + assert objs_empty_list.equals( + objs_empty_dict + ), "Empty list and empty dict should return same results" -def test_all_objects_filter_tags_dict_copy_behavior(): - """Test that filter_tags dict is copied and not modified in place.""" +def test_filter_by_tags_dict_not_modified(): + """Test that _filter_by_tags doesn't modify the original dict in place.""" original_filter = {"A": "1"} original_copy = original_filter.copy() - # Call all_objects with the filter - all_objects( - package_name="skbase", - filter_tags=original_filter, - ) - - # Original dict should be unchanged - assert original_filter == original_copy, "Original filter_tags dict should not be modified" - + # Call _filter_by_tags with the filter - this happens inside + # all_objects/get_package_metadata + from skbase.tests.conftest import Parent -def test_get_package_metadata_filter_tags_dict_copy_behavior(): - """Test that tag_filter dict is copied and not modified in place.""" - original_filter = {"A": "1"} - original_copy = original_filter.copy() - - # Call get_package_metadata with the filter - get_package_metadata( - "skbase", - tag_filter=original_filter, - classes_to_exclude=TagAliaserMixin, - ) + _filter_by_tags(Parent, tag_filter=original_filter) # Original dict should be unchanged - assert original_filter == original_copy, "Original tag_filter dict should not be modified" - + assert ( + original_filter == original_copy + ), "Original filter_tags dict should not be modified" From fa756ba767b08a4d87f185ee19a56d16c39df6a0 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Sat, 7 Jun 2025 20:25:53 +0000 Subject: [PATCH 5/5] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- skbase/lookup/tests/test_lookup.py | 41 ++++++++++++++++++++---------- 1 file changed, 27 insertions(+), 14 deletions(-) diff --git a/skbase/lookup/tests/test_lookup.py b/skbase/lookup/tests/test_lookup.py index 29f0a20e..befc0933 100644 --- a/skbase/lookup/tests/test_lookup.py +++ b/skbase/lookup/tests/test_lookup.py @@ -1096,7 +1096,9 @@ def test_all_objects_filter_tags_string_preprocessing(): ) # Results should be identical - assert objs_str.equals(objs_dict), "String and dict filter should return same results" + assert objs_str.equals( + objs_dict + ), "String and dict filter should return same results" def test_all_objects_filter_tags_list_preprocessing(): @@ -1117,7 +1119,9 @@ def test_all_objects_filter_tags_list_preprocessing(): ) # Results should be identical - assert objs_list.equals(objs_dict), "List and dict filter should return same results" + assert objs_list.equals( + objs_dict + ), "List and dict filter should return same results" def test_all_objects_filter_tags_tuple_preprocessing(): @@ -1138,7 +1142,9 @@ def test_all_objects_filter_tags_tuple_preprocessing(): ) # Results should be identical - assert objs_tuple.equals(objs_dict), "Tuple and dict filter should return same results" + assert objs_tuple.equals( + objs_dict + ), "Tuple and dict filter should return same results" def test_get_package_metadata_filter_tags_string_preprocessing(): @@ -1149,14 +1155,14 @@ def test_get_package_metadata_filter_tags_string_preprocessing(): tag_filter="A", classes_to_exclude=TagAliaserMixin, ) - + result_dict = get_package_metadata( "skbase", - modules_to_ignore="skbase", + modules_to_ignore="skbase", tag_filter={"A": True}, classes_to_exclude=TagAliaserMixin, ) - + # Results should be identical assert result_str.keys() == result_dict.keys() @@ -1169,14 +1175,14 @@ def test_get_package_metadata_filter_tags_list_preprocessing(): tag_filter=["A", "B"], classes_to_exclude=TagAliaserMixin, ) - + result_dict = get_package_metadata( "skbase", - modules_to_ignore="skbase", + modules_to_ignore="skbase", tag_filter={"A": True, "B": True}, classes_to_exclude=TagAliaserMixin, ) - + # Results should be identical assert result_list.keys() == result_dict.keys() @@ -1193,7 +1199,9 @@ def test_get_package_metadata_filter_tags_list_preprocessing(): ) def test_all_objects_filter_tags_invalid_types_preprocessing(invalid_filter): """Test that invalid filter_tags types raise TypeError in all_objects.""" - with pytest.raises(TypeError, match="filter_tags must be a str, list of str, or dict"): + with pytest.raises( + TypeError, match="filter_tags must be a str, list of str, or dict" + ): all_objects( package_name="skbase", filter_tags=invalid_filter, @@ -1212,7 +1220,9 @@ def test_all_objects_filter_tags_invalid_types_preprocessing(invalid_filter): ) def test_get_package_metadata_filter_tags_invalid_types_preprocessing(invalid_filter): """Test that invalid tag_filter types raise TypeError in get_package_metadata.""" - with pytest.raises(TypeError, match="tag_filter must be a str, list of str, or dict"): + with pytest.raises( + TypeError, match="tag_filter must be a str, list of str, or dict" + ): get_package_metadata( "skbase", tag_filter=invalid_filter, @@ -1253,7 +1263,9 @@ def test_filter_by_tags_dict_not_modified(): ) # Original dict should be unchanged - assert original_filter == original_copy, "Original filter_tags dict should not be modified" + assert ( + original_filter == original_copy + ), "Original filter_tags dict should not be modified" def test_get_package_metadata_filter_tags_dict_copy_behavior(): @@ -1269,5 +1281,6 @@ def test_get_package_metadata_filter_tags_dict_copy_behavior(): ) # Original dict should be unchanged - assert original_filter == original_copy, "Original tag_filter dict should not be modified" - + assert ( + original_filter == original_copy + ), "Original tag_filter dict should not be modified"