scikit-base 1.2.0__tar.gz → 1.2.1__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (82) hide show
  1. {scikit_base-1.2.0/scikit_base.egg-info → scikit_base-1.2.1}/PKG-INFO +5 -6
  2. {scikit_base-1.2.0 → scikit_base-1.2.1}/README.md +1 -1
  3. {scikit_base-1.2.0 → scikit_base-1.2.1}/pyproject.toml +4 -5
  4. {scikit_base-1.2.0 → scikit_base-1.2.1/scikit_base.egg-info}/PKG-INFO +5 -6
  5. {scikit_base-1.2.0 → scikit_base-1.2.1}/skbase/__init__.py +1 -1
  6. {scikit_base-1.2.0 → scikit_base-1.2.1}/skbase/base/_tagmanager.py +20 -22
  7. {scikit_base-1.2.0 → scikit_base-1.2.1}/skbase/lookup/_lookup.py +55 -10
  8. {scikit_base-1.2.0 → scikit_base-1.2.1}/skbase/lookup/tests/test_lookup.py +41 -0
  9. {scikit_base-1.2.0 → scikit_base-1.2.1}/skbase/tests/conftest.py +3 -0
  10. {scikit_base-1.2.0 → scikit_base-1.2.1}/skbase/utils/deep_equals/_deep_equals.py +15 -3
  11. {scikit_base-1.2.0 → scikit_base-1.2.1}/skbase/utils/tests/test_deep_equals.py +47 -0
  12. {scikit_base-1.2.0 → scikit_base-1.2.1}/LICENSE +0 -0
  13. {scikit_base-1.2.0 → scikit_base-1.2.1}/scikit_base.egg-info/SOURCES.txt +0 -0
  14. {scikit_base-1.2.0 → scikit_base-1.2.1}/scikit_base.egg-info/dependency_links.txt +0 -0
  15. {scikit_base-1.2.0 → scikit_base-1.2.1}/scikit_base.egg-info/requires.txt +0 -0
  16. {scikit_base-1.2.0 → scikit_base-1.2.1}/scikit_base.egg-info/top_level.txt +0 -0
  17. {scikit_base-1.2.0 → scikit_base-1.2.1}/scikit_base.egg-info/zip-safe +0 -0
  18. {scikit_base-1.2.0 → scikit_base-1.2.1}/setup.cfg +0 -0
  19. {scikit_base-1.2.0 → scikit_base-1.2.1}/skbase/_exceptions.py +0 -0
  20. {scikit_base-1.2.0 → scikit_base-1.2.1}/skbase/_nopytest_tests.py +0 -0
  21. {scikit_base-1.2.0 → scikit_base-1.2.1}/skbase/base/__init__.py +0 -0
  22. {scikit_base-1.2.0 → scikit_base-1.2.1}/skbase/base/_base.py +0 -0
  23. {scikit_base-1.2.0 → scikit_base-1.2.1}/skbase/base/_clone_base.py +0 -0
  24. {scikit_base-1.2.0 → scikit_base-1.2.1}/skbase/base/_clone_plugins.py +0 -0
  25. {scikit_base-1.2.0 → scikit_base-1.2.1}/skbase/base/_meta.py +0 -0
  26. {scikit_base-1.2.0 → scikit_base-1.2.1}/skbase/base/_pretty_printing/__init__.py +0 -0
  27. {scikit_base-1.2.0 → scikit_base-1.2.1}/skbase/base/_pretty_printing/_object_html_repr.py +0 -0
  28. {scikit_base-1.2.0 → scikit_base-1.2.1}/skbase/base/_pretty_printing/_pprint.py +0 -0
  29. {scikit_base-1.2.0 → scikit_base-1.2.1}/skbase/base/_pretty_printing/tests/__init__.py +0 -0
  30. {scikit_base-1.2.0 → scikit_base-1.2.1}/skbase/base/_pretty_printing/tests/test_object_html_repr.py +0 -0
  31. {scikit_base-1.2.0 → scikit_base-1.2.1}/skbase/base/_pretty_printing/tests/test_pprint.py +0 -0
  32. {scikit_base-1.2.0 → scikit_base-1.2.1}/skbase/lookup/__init__.py +0 -0
  33. {scikit_base-1.2.0 → scikit_base-1.2.1}/skbase/lookup/tests/__init__.py +0 -0
  34. {scikit_base-1.2.0 → scikit_base-1.2.1}/skbase/testing/__init__.py +0 -0
  35. {scikit_base-1.2.0 → scikit_base-1.2.1}/skbase/testing/test_all_objects.py +0 -0
  36. {scikit_base-1.2.0 → scikit_base-1.2.1}/skbase/testing/tests/__init__.py +0 -0
  37. {scikit_base-1.2.0 → scikit_base-1.2.1}/skbase/testing/tests/test_quicktester.py +0 -0
  38. {scikit_base-1.2.0 → scikit_base-1.2.1}/skbase/testing/utils/__init__.py +0 -0
  39. {scikit_base-1.2.0 → scikit_base-1.2.1}/skbase/testing/utils/_conditional_fixtures.py +0 -0
  40. {scikit_base-1.2.0 → scikit_base-1.2.1}/skbase/testing/utils/inspect.py +0 -0
  41. {scikit_base-1.2.0 → scikit_base-1.2.1}/skbase/tests/__init__.py +0 -0
  42. {scikit_base-1.2.0 → scikit_base-1.2.1}/skbase/tests/mock_package/__init__.py +0 -0
  43. {scikit_base-1.2.0 → scikit_base-1.2.1}/skbase/tests/mock_package/test_mock_package.py +0 -0
  44. {scikit_base-1.2.0 → scikit_base-1.2.1}/skbase/tests/test_base.py +0 -0
  45. {scikit_base-1.2.0 → scikit_base-1.2.1}/skbase/tests/test_baseestimator.py +0 -0
  46. {scikit_base-1.2.0 → scikit_base-1.2.1}/skbase/tests/test_deep_equals.py +0 -0
  47. {scikit_base-1.2.0 → scikit_base-1.2.1}/skbase/tests/test_exceptions.py +0 -0
  48. {scikit_base-1.2.0 → scikit_base-1.2.1}/skbase/tests/test_lookup.py +0 -0
  49. {scikit_base-1.2.0 → scikit_base-1.2.1}/skbase/tests/test_lookup_metaclasses.py +0 -0
  50. {scikit_base-1.2.0 → scikit_base-1.2.1}/skbase/tests/test_meta.py +0 -0
  51. {scikit_base-1.2.0 → scikit_base-1.2.1}/skbase/tests/test_tagaliaser.py +0 -0
  52. {scikit_base-1.2.0 → scikit_base-1.2.1}/skbase/utils/__init__.py +0 -0
  53. {scikit_base-1.2.0 → scikit_base-1.2.1}/skbase/utils/_check.py +0 -0
  54. {scikit_base-1.2.0 → scikit_base-1.2.1}/skbase/utils/_iter.py +0 -0
  55. {scikit_base-1.2.0 → scikit_base-1.2.1}/skbase/utils/_nested_iter.py +0 -0
  56. {scikit_base-1.2.0 → scikit_base-1.2.1}/skbase/utils/_utils.py +0 -0
  57. {scikit_base-1.2.0 → scikit_base-1.2.1}/skbase/utils/deep_equals/__init__.py +0 -0
  58. {scikit_base-1.2.0 → scikit_base-1.2.1}/skbase/utils/deep_equals/_common.py +0 -0
  59. {scikit_base-1.2.0 → scikit_base-1.2.1}/skbase/utils/dependencies/__init__.py +0 -0
  60. {scikit_base-1.2.0 → scikit_base-1.2.1}/skbase/utils/dependencies/_dependencies.py +0 -0
  61. {scikit_base-1.2.0 → scikit_base-1.2.1}/skbase/utils/dependencies/_import.py +0 -0
  62. {scikit_base-1.2.0 → scikit_base-1.2.1}/skbase/utils/dependencies/tests/__init__.py +0 -0
  63. {scikit_base-1.2.0 → scikit_base-1.2.1}/skbase/utils/dependencies/tests/test_check_dependencies.py +0 -0
  64. {scikit_base-1.2.0 → scikit_base-1.2.1}/skbase/utils/dependencies/tests/test_safe_import.py +0 -0
  65. {scikit_base-1.2.0 → scikit_base-1.2.1}/skbase/utils/doctest_run.py +0 -0
  66. {scikit_base-1.2.0 → scikit_base-1.2.1}/skbase/utils/git_diff.py +0 -0
  67. {scikit_base-1.2.0 → scikit_base-1.2.1}/skbase/utils/random_state.py +0 -0
  68. {scikit_base-1.2.0 → scikit_base-1.2.1}/skbase/utils/stderr_mute.py +0 -0
  69. {scikit_base-1.2.0 → scikit_base-1.2.1}/skbase/utils/stdout_mute.py +0 -0
  70. {scikit_base-1.2.0 → scikit_base-1.2.1}/skbase/utils/tests/__init__.py +0 -0
  71. {scikit_base-1.2.0 → scikit_base-1.2.1}/skbase/utils/tests/test_check.py +0 -0
  72. {scikit_base-1.2.0 → scikit_base-1.2.1}/skbase/utils/tests/test_iter.py +0 -0
  73. {scikit_base-1.2.0 → scikit_base-1.2.1}/skbase/utils/tests/test_nested_iter.py +0 -0
  74. {scikit_base-1.2.0 → scikit_base-1.2.1}/skbase/utils/tests/test_random_state.py +0 -0
  75. {scikit_base-1.2.0 → scikit_base-1.2.1}/skbase/utils/tests/test_std_mute.py +0 -0
  76. {scikit_base-1.2.0 → scikit_base-1.2.1}/skbase/utils/tests/test_utils.py +0 -0
  77. {scikit_base-1.2.0 → scikit_base-1.2.1}/skbase/validate/__init__.py +0 -0
  78. {scikit_base-1.2.0 → scikit_base-1.2.1}/skbase/validate/_named_objects.py +0 -0
  79. {scikit_base-1.2.0 → scikit_base-1.2.1}/skbase/validate/_type_checks.py +0 -0
  80. {scikit_base-1.2.0 → scikit_base-1.2.1}/skbase/validate/tests/__init__.py +0 -0
  81. {scikit_base-1.2.0 → scikit_base-1.2.1}/skbase/validate/tests/test_iterable_named_objects.py +0 -0
  82. {scikit_base-1.2.0 → scikit_base-1.2.1}/skbase/validate/tests/test_type_validations.py +0 -0
@@ -1,10 +1,10 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: scikit-base
3
- Version: 1.2.0
3
+ Version: 1.2.1
4
4
  Summary: Base classes for sklearn-like parametric objects
5
- Author-email: sktime developers <sktime.toolbox@gmail.com>
5
+ Author-email: sktime developers <info@sktime.net>
6
6
  Maintainer: Franz Király
7
- Maintainer-email: sktime developers <sktime.toolbox@gmail.com>
7
+ Maintainer-email: sktime developers <info@sktime.net>
8
8
  License: BSD 3-Clause License
9
9
 
10
10
  Copyright (c) 2022, skbase Developers
@@ -50,13 +50,12 @@ Classifier: Operating System :: Microsoft :: Windows
50
50
  Classifier: Operating System :: POSIX
51
51
  Classifier: Operating System :: Unix
52
52
  Classifier: Operating System :: MacOS
53
- Classifier: Programming Language :: Python :: 3.10
54
53
  Classifier: Programming Language :: Python :: 3.11
55
54
  Classifier: Programming Language :: Python :: 3.12
56
55
  Classifier: Programming Language :: Python :: 3.13
57
56
  Classifier: Programming Language :: Python :: 3.14
58
57
  Classifier: Programming Language :: Python :: 3.15
59
- Requires-Python: <3.16,>=3.10
58
+ Requires-Python: <3.16,>=3.11
60
59
  Description-Content-Type: text/markdown
61
60
  License-File: LICENSE
62
61
  Provides-Extra: all-extras
@@ -116,7 +115,7 @@ Dynamic: license-file
116
115
  `skbase` provides base classes for creating scikit-learn-like parametric objects,
117
116
  along with tools to make it easier to build your own packages that follow these design patterns.
118
117
 
119
- :rocket: Version 1.2.0 is now available. Check out our
118
+ :rocket: Version 1.2.1 is now available. Check out our
120
119
  [release notes](https://skbase.readthedocs.io/en/latest/changelog.html).
121
120
 
122
121
  | Overview | |
@@ -7,7 +7,7 @@
7
7
  `skbase` provides base classes for creating scikit-learn-like parametric objects,
8
8
  along with tools to make it easier to build your own packages that follow these design patterns.
9
9
 
10
- :rocket: Version 1.2.0 is now available. Check out our
10
+ :rocket: Version 1.2.1 is now available. Check out our
11
11
  [release notes](https://skbase.readthedocs.io/en/latest/changelog.html).
12
12
 
13
13
  | Overview | |
@@ -1,12 +1,12 @@
1
1
  [project]
2
2
  name = "scikit-base"
3
- version = "1.2.0"
3
+ version = "1.2.1"
4
4
  description = "Base classes for sklearn-like parametric objects"
5
5
  authors = [
6
- {name = "sktime developers", email = "sktime.toolbox@gmail.com"},
6
+ {name = "sktime developers", email = "info@sktime.net"},
7
7
  ]
8
8
  maintainers = [
9
- {name = "sktime developers", email = "sktime.toolbox@gmail.com"},
9
+ {name = "sktime developers", email = "info@sktime.net"},
10
10
  {name = "Franz Király"},
11
11
  ]
12
12
  readme = "README.md"
@@ -26,14 +26,13 @@ classifiers = [
26
26
  "Operating System :: POSIX",
27
27
  "Operating System :: Unix",
28
28
  "Operating System :: MacOS",
29
- "Programming Language :: Python :: 3.10",
30
29
  "Programming Language :: Python :: 3.11",
31
30
  "Programming Language :: Python :: 3.12",
32
31
  "Programming Language :: Python :: 3.13",
33
32
  "Programming Language :: Python :: 3.14",
34
33
  "Programming Language :: Python :: 3.15",
35
34
  ]
36
- requires-python = ">=3.10,<3.16"
35
+ requires-python = ">=3.11,<3.16"
37
36
  dependencies = []
38
37
 
39
38
  [project.optional-dependencies]
@@ -1,10 +1,10 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: scikit-base
3
- Version: 1.2.0
3
+ Version: 1.2.1
4
4
  Summary: Base classes for sklearn-like parametric objects
5
- Author-email: sktime developers <sktime.toolbox@gmail.com>
5
+ Author-email: sktime developers <info@sktime.net>
6
6
  Maintainer: Franz Király
7
- Maintainer-email: sktime developers <sktime.toolbox@gmail.com>
7
+ Maintainer-email: sktime developers <info@sktime.net>
8
8
  License: BSD 3-Clause License
9
9
 
10
10
  Copyright (c) 2022, skbase Developers
@@ -50,13 +50,12 @@ Classifier: Operating System :: Microsoft :: Windows
50
50
  Classifier: Operating System :: POSIX
51
51
  Classifier: Operating System :: Unix
52
52
  Classifier: Operating System :: MacOS
53
- Classifier: Programming Language :: Python :: 3.10
54
53
  Classifier: Programming Language :: Python :: 3.11
55
54
  Classifier: Programming Language :: Python :: 3.12
56
55
  Classifier: Programming Language :: Python :: 3.13
57
56
  Classifier: Programming Language :: Python :: 3.14
58
57
  Classifier: Programming Language :: Python :: 3.15
59
- Requires-Python: <3.16,>=3.10
58
+ Requires-Python: <3.16,>=3.11
60
59
  Description-Content-Type: text/markdown
61
60
  License-File: LICENSE
62
61
  Provides-Extra: all-extras
@@ -116,7 +115,7 @@ Dynamic: license-file
116
115
  `skbase` provides base classes for creating scikit-learn-like parametric objects,
117
116
  along with tools to make it easier to build your own packages that follow these design patterns.
118
117
 
119
- :rocket: Version 1.2.0 is now available. Check out our
118
+ :rocket: Version 1.2.1 is now available. Check out our
120
119
  [release notes](https://skbase.readthedocs.io/en/latest/changelog.html).
121
120
 
122
121
  | Overview | |
@@ -7,4 +7,4 @@ The included functionality makes it easy to reuse scikit-learn and
7
7
  sktime design principles in your project.
8
8
  """
9
9
 
10
- __version__: str = "1.2.0"
10
+ __version__: str = "1.2.1"
@@ -15,24 +15,6 @@ from functools import lru_cache
15
15
  class _FlagManager:
16
16
  """Mixin class for flag and configuration settings management."""
17
17
 
18
- @staticmethod
19
- @lru_cache(maxsize=None)
20
- def _collect_class_flags_cached(cls, flag_attr_name):
21
- """Walk the MRO once per (cls, flag_attr_name) and cache the result.
22
-
23
- Returns the dict directly; callers are responsible for copying it
24
- before handing it out or mutating it, since this is a shared cached
25
- object.
26
- """
27
- collected_flags = {}
28
- for parent_class in reversed(inspect.getmro(cls)):
29
- if parent_class is object:
30
- continue
31
- if flag_attr_name in parent_class.__dict__:
32
- more_flags = parent_class.__dict__[flag_attr_name]
33
- collected_flags.update(more_flags)
34
- return collected_flags
35
-
36
18
  @classmethod
37
19
  def _get_class_flags(cls, flag_attr_name="_flags"):
38
20
  """Get class flags from estimator class and all its parent classes.
@@ -49,7 +31,7 @@ class _FlagManager:
49
31
  class attribute via nested inheritance. NOT overridden by dynamic
50
32
  flags set by set_flags or clone_flags.
51
33
  """
52
- cached = cls._collect_class_flags_cached(cls, flag_attr_name)
34
+ cached = _collect_class_flags_cached(cls, flag_attr_name)
53
35
  # shallow copy: cheap, and safe because tag values are immutable
54
36
  # (they are primitives types bool, etc.)
55
37
  return dict(cached)
@@ -112,9 +94,7 @@ class _FlagManager:
112
94
  and new flags from [flag_attr_name]_dynamic object attribute.
113
95
  """
114
96
  # shallow copy of the cached class-level dict
115
- collected_flags = dict(
116
- self._collect_class_flags_cached(type(self), flag_attr_name)
117
- )
97
+ collected_flags = dict(_collect_class_flags_cached(type(self), flag_attr_name))
118
98
 
119
99
  if hasattr(self, f"{flag_attr_name}_dynamic"):
120
100
  collected_flags.update(getattr(self, f"{flag_attr_name}_dynamic"))
@@ -229,3 +209,21 @@ class _FlagManager:
229
209
  self._set_flags(flag_attr_name=flag_attr_name, **update_dict)
230
210
 
231
211
  return self
212
+
213
+
214
+ @lru_cache(maxsize=None)
215
+ def _collect_class_flags_cached(cls, flag_attr_name):
216
+ """Walk the MRO once per (cls, flag_attr_name) and cache the result.
217
+
218
+ Returns the dict directly; callers are responsible for copying it
219
+ before handing it out or mutating it, since this is a shared cached
220
+ object.
221
+ """
222
+ collected_flags = {}
223
+ for parent_class in reversed(inspect.getmro(cls)):
224
+ if parent_class is object:
225
+ continue
226
+ if flag_attr_name in parent_class.__dict__:
227
+ more_flags = parent_class.__dict__[flag_attr_name]
228
+ collected_flags.update(more_flags)
229
+ return collected_flags
@@ -236,7 +236,12 @@ def _filter_by_tags(obj, tag_filter=None, as_dataframe=True):
236
236
 
237
237
  # search value matches tag value iff
238
238
  # at least one element of search value matches at least one element of tag value
239
- str_match = len(set(search_str).intersection(tag_value)) > 0
239
+ try:
240
+ str_match = len(set(search_str).intersection(tag_value)) > 0
241
+ except TypeError:
242
+ # only reached if there are unhashable elements, e.g., nested lists
243
+ # in python_dependencies, otherwise the fast set lookup is used
244
+ str_match = _any_element_equal(search_str, tag_value)
240
245
  re_match = any(s.fullmatch(str(tag)) for s in search_re for tag in tag_value)
241
246
  match = str_match or re_match
242
247
 
@@ -245,6 +250,42 @@ def _filter_by_tags(obj, tag_filter=None, as_dataframe=True):
245
250
  return cond_sat
246
251
 
247
252
 
253
+ def _is_hashable(obj):
254
+ """Return whether obj is hashable."""
255
+ try:
256
+ hash(obj)
257
+ except TypeError:
258
+ return False
259
+ return True
260
+
261
+
262
+ def _any_element_equal(left, right):
263
+ """Check whether any element of left is equal to any element of right.
264
+
265
+ Hashable elements are compared via set lookup, in linear time.
266
+ Only unhashable elements, e.g., nested lists, are compared pairwise,
267
+ against all elements of the other iterable.
268
+
269
+ Parameters
270
+ ----------
271
+ left, right : list
272
+ lists of elements to compare, may contain unhashable elements
273
+
274
+ Returns
275
+ -------
276
+ bool, whether there is an element of left equal to an element of right
277
+ """
278
+ left_hashable = {x for x in left if _is_hashable(x)}
279
+ left_unhashable = [x for x in left if not _is_hashable(x)]
280
+ right_unhashable = [y for y in right if not _is_hashable(y)]
281
+
282
+ if any(y in left_hashable for y in right if _is_hashable(y)):
283
+ return True
284
+ if any(x == y for x in left_unhashable for y in right):
285
+ return True
286
+ return any(x == y for x in left for y in right_unhashable)
287
+
288
+
248
289
  def _walk(root, exclude=None, prefix=""):
249
290
  """Recursively return all modules and sub-modules as list of strings.
250
291
 
@@ -776,6 +817,7 @@ def all_objects(
776
817
  Parameters
777
818
  ----------
778
819
  object_types: class or tuple, list of classes, default=None
820
+ Which kind of objects should be returned.
779
821
 
780
822
  - If class_lookup is provided, can also be str or list of str
781
823
  which kind of objects should be returned.
@@ -786,6 +828,7 @@ def all_objects(
786
828
  as `class_lookup` parameter is passed a lookup dict.
787
829
 
788
830
  return_names: bool, default=True
831
+ Whether to include the class name strings in the returned objects.
789
832
 
790
833
  - If True, estimator class name is included in the ``all_objects``
791
834
  return in the order: name, estimator class, optional tags, either as
@@ -793,10 +836,11 @@ def all_objects(
793
836
  - If False, estimator class name is removed from the ``all_objects`` return.
794
837
 
795
838
  filter_tags: str, list[str] or dict[str, Any], default=None
796
- Filter used to determine if ``klass`` has tag or expected tag values.
839
+ Filter condition to filter returned objects based on their tags.
840
+ The filter condition is used to determine which objects are returned.
797
841
 
798
842
  - If a str or list of strings is provided, the return will be filtered
799
- to keep classes that have all the tag(s) specified by the strings,
843
+ to keep exactly classes that have all the tag(s) specified by the strings,
800
844
  with the tag value being True.
801
845
 
802
846
  - If a dict is provided, the return will be filtered to keep exactly the classes
@@ -821,15 +865,16 @@ def all_objects(
821
865
  applied to ``tag_value``.
822
866
 
823
867
  exclude_objects: str or list[str], default=None
824
- Names of estimators to exclude.
868
+ Names of objects to exclude.
825
869
 
826
870
  as_dataframe: bool, default=False
871
+ Whether to return the results as a pandas DataFrame or as a list.
827
872
 
828
- - If False, `all_objects` will return a list (either a list of
829
- `skbase` objects or a list of tuples, see Returns).
830
- - If True, `all_objects` will return a `pandas.DataFrame` with named
831
- columns for all of the attributes being returned.
832
- this requires soft dependency `pandas` to be installed.
873
+ - If False, ``all_objectss`` will return a list - either a list of
874
+ ``scikit-base`` compatible classes or a list of tuples, see Returns.
875
+ - If True, ``all_objects`` will return a ``pandas.DataFrame`` with named
876
+ columns for all of the attributes being returned.
877
+ this requires soft dependency ``pandas`` to be installed.
833
878
 
834
879
  return_tags: str or list of str, default=None
835
880
  Names of tags to fetch and return each object's value of. The tag values
@@ -871,7 +916,7 @@ def all_objects(
871
916
 
872
917
  - a pandas.DataFrame if ``as_dataframe=True``, with columns:
873
918
 
874
- - "names" with the returned class names if ``return_name=True``
919
+ - "names" with the returned class names if ``return_names=True``
875
920
  - "objects" with returned classes.
876
921
  - optional columns named based on tags passed in ``return_tags``
877
922
  if ``return_tags is not None``.
@@ -20,6 +20,7 @@ from skbase.base import BaseEstimator, BaseObject
20
20
  from skbase.base._base import TagAliaserMixin
21
21
  from skbase.lookup import all_objects, get_package_metadata
22
22
  from skbase.lookup._lookup import (
23
+ _any_element_equal,
23
24
  _determine_module_path,
24
25
  _filter_by_class,
25
26
  _filter_by_tags,
@@ -411,6 +412,46 @@ def test_filter_by_tags():
411
412
  assert _filter_by_tags(Parent, {7: 11})
412
413
 
413
414
 
415
+ def test_filter_by_tags_unhashable_tag_value():
416
+ """Test _filter_by_tags does not fail if a tag value contains unhashable items.
417
+
418
+ For instance, python_dependencies can be a list containing a nested list.
419
+ """
420
+
421
+ class ClassWithNestedListTag(BaseObject):
422
+ _tags = {"python_dependencies": ["pkg_a", ["pkg_b>=1.0", "pkg_c<2.0"]]}
423
+
424
+ assert _filter_by_tags(ClassWithNestedListTag, {"python_dependencies": "pkg_a"})
425
+ assert not _filter_by_tags(ClassWithNestedListTag, {"python_dependencies": "pkg_b"})
426
+ assert _filter_by_tags(
427
+ ClassWithNestedListTag, {"python_dependencies": ["pkg_x", "pkg_a"]}
428
+ )
429
+ # nested lists can be matched, too
430
+ assert _filter_by_tags(
431
+ ClassWithNestedListTag,
432
+ {"python_dependencies": [["pkg_b>=1.0", "pkg_c<2.0"]]},
433
+ )
434
+
435
+
436
+ def test_any_element_equal():
437
+ """Test _any_element_equal with hashable and unhashable elements."""
438
+ # hashable only
439
+ assert _any_element_equal(["a", "b"], ["c", "b"])
440
+ assert not _any_element_equal(["a", "b"], ["c", "d"])
441
+ assert not _any_element_equal([], ["a"])
442
+
443
+ # unhashable elements in right, left, or both
444
+ assert _any_element_equal(["a"], [["x"], "a"])
445
+ assert not _any_element_equal(["x"], [["x"], "a"])
446
+ assert _any_element_equal([["x"], "b"], ["c", ["x"]])
447
+ assert _any_element_equal([{"k": 1}], [{"k": 1}])
448
+ assert not _any_element_equal([["x"]], [("x",), "x"])
449
+
450
+ # hashable equal to unhashable, e.g., frozenset and set
451
+ assert _any_element_equal([frozenset({1})], [{1}])
452
+ assert _any_element_equal([{1}], [frozenset({1})])
453
+
454
+
414
455
  def test_walk_returns_expected_format(fixture_skbase_root_path):
415
456
  """Check walk function returns expected format."""
416
457
 
@@ -216,6 +216,7 @@ SKBASE_FUNCTIONS_BY_MODULE.update(
216
216
  "_get_sklearn_clone",
217
217
  "_is_sklearn_present",
218
218
  ),
219
+ "skbase.base._tagmanager": ("_collect_class_flags_cached",),
219
220
  "skbase.base._pretty_printing._object_html_repr": (
220
221
  "_get_visual_block",
221
222
  "_object_html_repr",
@@ -226,6 +227,7 @@ SKBASE_FUNCTIONS_BY_MODULE.update(
226
227
  "skbase.lookup._lookup": (
227
228
  "all_objects",
228
229
  "get_package_metadata",
230
+ "_any_element_equal",
229
231
  "_check_object_types",
230
232
  "_coerce_to_tuple",
231
233
  "_determine_module_path",
@@ -235,6 +237,7 @@ SKBASE_FUNCTIONS_BY_MODULE.update(
235
237
  "_get_module_info",
236
238
  "_get_return_tags",
237
239
  "_import_module",
240
+ "_is_hashable",
238
241
  "_is_ignored_module",
239
242
  "_is_non_public_module",
240
243
  "_make_dataframe",
@@ -70,10 +70,21 @@ def deep_equals(x, y, return_msg=False, plugins=None):
70
70
  .dtype - dtype of pandas or numpy object is not equal
71
71
  .index - index of pandas object is not equal
72
72
  .series_equals, .df_equals, .index_equals - .equals of pd returns False
73
- [i] - if tuple/list: i-th element not equal
73
+ [i] - if tuple/list or object-dtype array: i-th element not equal
74
+ (arrays are flattened before comparison)
74
75
  [key] - if dict: value at key is not equal
75
76
  [colname] - if pandas.DataFrame: column with name colname is not equal
76
77
  != - call to generic != returns False
78
+
79
+ Examples
80
+ --------
81
+ >>> import numpy as np
82
+ >>> x = np.array([1, 2], dtype=object)
83
+ >>> y = np.array([1, 3], dtype=object)
84
+ >>> deep_equals(x, y)
85
+ False
86
+ >>> deep_equals(x, y, return_msg=True)
87
+ (False, '[1] !=, 2 != 3')
77
88
  """
78
89
  # call deep_equals_custom with default plugins
79
90
  plugins_default = [
@@ -149,8 +160,9 @@ def _numpy_equals_plugin(x, y, return_msg=False, deep_equals=None):
149
160
  y_flat = y.flatten()
150
161
  for i in range(len(x_flat)):
151
162
  is_equal, msg = deep_equals(x_flat[i], y_flat[i], return_msg=True)
152
- return ret(is_equal, f"[{i}]" + msg)
153
- return ret(True, "") # catches len(x_flat) == 0
163
+ if not is_equal:
164
+ return ret(False, f"[{i}]" + msg)
165
+ return ret(True, "")
154
166
  else:
155
167
  return ret(np.array_equal(x, y, equal_nan=True), ".values")
156
168
 
@@ -131,6 +131,53 @@ def copy_except_if_sklearn(obj):
131
131
  return deepcopy(obj)
132
132
 
133
133
 
134
+ @pytest.mark.skipif(
135
+ not _check_soft_dependencies("numpy", severity="none"),
136
+ reason="numpy not available",
137
+ )
138
+ @pytest.mark.parametrize("shape", [(3,), (1, 3), (3, 1)])
139
+ @pytest.mark.parametrize("difference_index", [0, 1, 2])
140
+ @pytest.mark.parametrize("nested", [False, True])
141
+ def test_deep_equals_numpy_object_array_all_elements(shape, difference_index, nested):
142
+ """Compare every object-array element, including nested arrays."""
143
+ import numpy as np
144
+
145
+ x = np.empty(3, dtype=object)
146
+ for i in range(3):
147
+ x[i] = np.array([i, i + 1]) if nested else i
148
+ x = x.reshape(shape)
149
+ y = deepcopy(x)
150
+
151
+ assert deep_equals(x, y) is True
152
+ assert deep_equals(x, y, return_msg=True) == (True, "")
153
+
154
+ if nested:
155
+ y.flat[difference_index][1] += 1
156
+ else:
157
+ y.flat[difference_index] += 1
158
+
159
+ assert deep_equals(x, y) is False
160
+ is_equal, msg = deep_equals(x, y, return_msg=True)
161
+ assert is_equal is False
162
+ assert msg.startswith(f"[{difference_index}]")
163
+
164
+
165
+ @pytest.mark.skipif(
166
+ not _check_soft_dependencies("numpy", severity="none"),
167
+ reason="numpy not available",
168
+ )
169
+ @pytest.mark.parametrize("shape", [(), (0,), (0, 2), (1,), (2, 2)])
170
+ def test_deep_equals_numpy_object_array_equal(shape):
171
+ """Preserve equality for scalar, empty and NaN-containing object arrays."""
172
+ import numpy as np
173
+
174
+ x = np.full(shape, np.nan, dtype=object)
175
+ y = deepcopy(x)
176
+
177
+ assert deep_equals(x, y) is True
178
+ assert deep_equals(x, y, return_msg=True) == (True, "")
179
+
180
+
134
181
  # Add JAX examples
135
182
  if _check_soft_dependencies("jax", severity="none"):
136
183
  import jax.numpy as jnp
File without changes
File without changes