scikit-base 0.4.6__py3-none-any.whl → 0.5.1__py3-none-any.whl

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 (60) hide show
  1. docs/source/conf.py +299 -299
  2. {scikit_base-0.4.6.dist-info → scikit_base-0.5.1.dist-info}/LICENSE +29 -29
  3. {scikit_base-0.4.6.dist-info → scikit_base-0.5.1.dist-info}/METADATA +160 -159
  4. scikit_base-0.5.1.dist-info/RECORD +58 -0
  5. {scikit_base-0.4.6.dist-info → scikit_base-0.5.1.dist-info}/WHEEL +1 -1
  6. scikit_base-0.5.1.dist-info/top_level.txt +5 -0
  7. {scikit_base-0.4.6.dist-info → scikit_base-0.5.1.dist-info}/zip-safe +1 -1
  8. skbase/__init__.py +14 -14
  9. skbase/_exceptions.py +31 -31
  10. skbase/_nopytest_tests.py +35 -35
  11. skbase/base/__init__.py +20 -20
  12. skbase/base/_base.py +1249 -1249
  13. skbase/base/_meta.py +883 -871
  14. skbase/base/_pretty_printing/__init__.py +11 -11
  15. skbase/base/_pretty_printing/_object_html_repr.py +392 -392
  16. skbase/base/_pretty_printing/_pprint.py +412 -412
  17. skbase/base/_tagmanager.py +217 -217
  18. skbase/lookup/__init__.py +31 -31
  19. skbase/lookup/_lookup.py +1009 -1009
  20. skbase/lookup/tests/__init__.py +2 -2
  21. skbase/lookup/tests/test_lookup.py +991 -991
  22. skbase/testing/__init__.py +12 -12
  23. skbase/testing/test_all_objects.py +852 -856
  24. skbase/testing/utils/__init__.py +5 -5
  25. skbase/testing/utils/_conditional_fixtures.py +209 -209
  26. skbase/testing/utils/_dependencies.py +15 -15
  27. skbase/testing/utils/deep_equals.py +15 -15
  28. skbase/testing/utils/inspect.py +30 -30
  29. skbase/testing/utils/tests/__init__.py +2 -2
  30. skbase/testing/utils/tests/test_check_dependencies.py +49 -49
  31. skbase/testing/utils/tests/test_deep_equals.py +66 -66
  32. skbase/tests/__init__.py +2 -2
  33. skbase/tests/conftest.py +273 -273
  34. skbase/tests/mock_package/__init__.py +5 -5
  35. skbase/tests/mock_package/test_mock_package.py +74 -74
  36. skbase/tests/test_base.py +1202 -1202
  37. skbase/tests/test_baseestimator.py +130 -130
  38. skbase/tests/test_exceptions.py +23 -23
  39. skbase/tests/test_meta.py +170 -131
  40. skbase/utils/__init__.py +21 -21
  41. skbase/utils/_check.py +53 -53
  42. skbase/utils/_iter.py +238 -238
  43. skbase/utils/_nested_iter.py +180 -180
  44. skbase/utils/_utils.py +91 -91
  45. skbase/utils/deep_equals.py +358 -358
  46. skbase/utils/dependencies/__init__.py +11 -11
  47. skbase/utils/dependencies/_dependencies.py +253 -253
  48. skbase/utils/tests/__init__.py +4 -4
  49. skbase/utils/tests/test_check.py +24 -24
  50. skbase/utils/tests/test_iter.py +127 -127
  51. skbase/utils/tests/test_nested_iter.py +84 -84
  52. skbase/utils/tests/test_utils.py +37 -37
  53. skbase/validate/__init__.py +22 -22
  54. skbase/validate/_named_objects.py +403 -403
  55. skbase/validate/_types.py +345 -345
  56. skbase/validate/tests/__init__.py +2 -2
  57. skbase/validate/tests/test_iterable_named_objects.py +200 -200
  58. skbase/validate/tests/test_type_validations.py +370 -370
  59. scikit_base-0.4.6.dist-info/RECORD +0 -58
  60. scikit_base-0.4.6.dist-info/top_level.txt +0 -2
skbase/validate/_types.py CHANGED
@@ -1,345 +1,345 @@
1
- #!/usr/bin/env python3 -u
2
- # -*- coding: utf-8 -*-
3
- # copyright: skbase developers, BSD-3-Clause License (see LICENSE file)
4
- """Tools for validating types."""
5
- import collections
6
- import inspect
7
- from typing import Any, List, Optional, Sequence, Tuple, Union
8
-
9
- from skbase.utils._iter import _format_seq_to_str, _remove_type_text, _scalar_to_seq
10
-
11
- __author__: List[str] = ["RNKuhns", "fkiraly"]
12
- __all__: List[str] = ["check_sequence", "check_type", "is_sequence"]
13
-
14
-
15
- def check_type(
16
- input_: Any,
17
- expected_type: type,
18
- allow_none: bool = False,
19
- input_name: Optional[str] = None,
20
- use_subclass: bool = False,
21
- ) -> Any:
22
- """Check the input is the expected type.
23
-
24
- Validates that the input is the type specified in `expected_type`, while optionally
25
- allowing None values as well (if ``allow_none=True``). For flexibility,
26
- the check can use ``issubclass`` instead of ``isinstance`` if ``use_subclass=True``.
27
-
28
- Parameters
29
- ----------
30
- input_ : Any
31
- The input to be type checked.
32
- expected_type : type
33
- The type that `input_` is expected to be.
34
- allow_none : bool, default=False
35
- Whether `input_` can be None in addition to being instance of `expected_type`.
36
- input_name : str, default=None
37
- The name to use when referring to `input_` in any raised error messages.
38
- If None, then "input" is used as `input_name`.
39
- use_subclass : bool, default=False
40
- Whether to check the type using issubclass instead of isinstance.
41
-
42
- - If True, then check uses issubclass.
43
- - If False (default), then check uses isinstance.
44
-
45
- Returns
46
- -------
47
- Any
48
- The input object.
49
-
50
- Raises
51
- ------
52
- TypeError
53
- If input does match expected type using isinstance by default
54
- or using issubclass in check if ``use_subclass=True``.
55
-
56
- Examples
57
- --------
58
- >>> from skbase.base import BaseEstimator, BaseObject
59
- >>> from skbase.validate import check_type
60
- >>> check_type(7, expected_type=int)
61
- 7
62
- >>> check_type(7.2, expected_type=(int, float))
63
- 7.2
64
- >>> check_type(BaseEstimator(), BaseObject)
65
- BaseEstimator()
66
- >>> check_type(BaseEstimator, expected_type=BaseObject, use_subclass=True)
67
- <class 'skbase.base._base.BaseEstimator'>
68
-
69
- An error is raised if the input is not the expected type
70
-
71
- >>> check_type(7, expected_type=str) # doctest: +SKIP
72
- TypeError: `input` should be type <class 'str'>, but found <class 'str'>.
73
- """
74
- # process expected_type parameter
75
- if not isinstance(expected_type, (type, tuple)):
76
- msg = " ".join(
77
- [
78
- "`expected_type` should be type or tuple[type, ...],"
79
- f"but found {_remove_type_text(expected_type)}."
80
- ]
81
- )
82
- raise TypeError(msg)
83
-
84
- # Assign default name to input_name parameter in case it is None
85
- if input_name is None:
86
- input_name = "input"
87
-
88
- # Check the type of input_
89
- type_check = issubclass if use_subclass else isinstance
90
- if (allow_none and input_ is None) or type_check(input_, expected_type):
91
- return input_
92
- else:
93
- chk_msg = "subclass type" if use_subclass else "be type"
94
- expected_type_str = _remove_type_text(expected_type)
95
- input_type_str = _remove_type_text(type(input_))
96
- if allow_none:
97
- type_msg = f"{expected_type_str} or None"
98
- else:
99
- type_msg = f"{expected_type_str}"
100
- raise TypeError(
101
- f"`{input_name}` should {chk_msg} {type_msg}, but found {input_type_str}."
102
- )
103
-
104
-
105
- def _convert_scalar_seq_type_input_to_tuple(
106
- type_input: Optional[Union[type, Tuple[type, ...]]],
107
- none_default: Optional[type] = None,
108
- type_input_subclass: Optional[type] = None,
109
- input_name: str = None,
110
- ) -> Tuple[type, ...]:
111
- """Convert input that is scalar or sequence of types to always be a tuple."""
112
- if none_default is None:
113
- none_default = collections.abc.Sequence
114
-
115
- seq_output: Tuple[type, ...]
116
- if type_input is None:
117
- seq_output = (none_default,)
118
- # if a sequence of types received as sequence_type, convert to tuple of types
119
- elif isinstance(type_input, collections.abc.Sequence) and all(
120
- isinstance(e, type) for e in type_input
121
- ):
122
- seq_output = tuple(type_input)
123
- elif (isinstance(type_input, type) or inspect.isclass(type_input)) and (
124
- type_input_subclass is None or issubclass(type_input, type_input_subclass)
125
- ):
126
- seq_output = (type_input,)
127
- else:
128
- name_str = "type_input" if input_name is None else input_name
129
- raise TypeError(f"`{name_str}` should be a type or tuple of types.")
130
-
131
- return seq_output
132
-
133
-
134
- def is_sequence(
135
- input_seq: Any,
136
- sequence_type: Optional[Union[type, Tuple[type, ...]]] = None,
137
- element_type: Optional[Union[type, Tuple[type, ...]]] = None,
138
- ) -> bool:
139
- """Indicate if an object is a sequence with optional check of element types.
140
-
141
- If `element_type` is supplied all elements are also checked against provided types.
142
-
143
- Parameters
144
- ----------
145
- input_seq : Any
146
- The input sequence to be validated.
147
- sequence_type : type or tuple[type, ...], default=None
148
- The allowed sequence type(s) that `input_seq` can be an instance of.
149
-
150
- - If None, then collections.abc.Sequence is used (all sequence types are valid)
151
- - If `sequence_type` is a type or tuple of types, then only the specified
152
- types are considered valid.
153
-
154
- element_type : type or tuple[type], default=None
155
- The allowed type(s) for elements of `input_seq`.
156
-
157
- - If None, then the elements of `input_seq` are not checked when determining
158
- if `input_seq` is a valid sequence.
159
- - If `element_type` is a type or tuple of types, then the elements of
160
- `input_seq` are checked to make sure they are all instances of
161
- the supplied `element_type`.
162
-
163
- Returns
164
- -------
165
- bool
166
- Whether the input is a valid sequence based on the supplied `sequence_type`
167
- and `element_type`.
168
-
169
- Examples
170
- --------
171
- >>> from skbase.base import BaseEstimator, BaseObject
172
- >>> from skbase.validate import is_sequence
173
- >>> is_sequence([1, 2, 3])
174
- True
175
- >>> is_sequence(7)
176
- False
177
-
178
- Generators are not sequences
179
-
180
- >>> is_sequence((c for c in [1, 2, 3]))
181
- False
182
-
183
- The expected sequence type can be included in the check
184
-
185
- >>> is_sequence([1, 2, 3, 4], sequence_type=list)
186
- True
187
- >>> is_sequence([1, 2, 3, 4], sequence_type=tuple)
188
- False
189
-
190
- The type of the elements can also be checked
191
-
192
- >>> is_sequence([1, 2, 3], element_type=int)
193
- True
194
- >>> is_sequence([1, 2, 3, 4], sequence_type=list, element_type=int)
195
- True
196
- >>> is_sequence([1, 2, 3, 4], sequence_type=list, element_type=float)
197
- False
198
- >>> is_sequence([1, 2, 3, 4], sequence_type=list, element_type=(int, float))
199
- True
200
- >>> is_sequence((BaseObject(), BaseEstimator()), element_type=BaseObject)
201
- True
202
- """
203
- sequence_type_ = _convert_scalar_seq_type_input_to_tuple(
204
- sequence_type,
205
- input_name="sequence_type",
206
- type_input_subclass=collections.abc.Sequence,
207
- )
208
-
209
- is_valid_sequence = isinstance(input_seq, sequence_type_)
210
-
211
- # Optionally verify elements have correct types
212
- if element_type is not None:
213
- element_type_ = _convert_scalar_seq_type_input_to_tuple(
214
- element_type, input_name="element_type"
215
- )
216
- element_types_okay = all(isinstance(e, element_type_) for e in input_seq)
217
- if not element_types_okay:
218
- is_valid_sequence = False
219
-
220
- return is_valid_sequence
221
-
222
-
223
- def check_sequence(
224
- input_seq: Sequence[Any],
225
- sequence_type: Optional[Union[type, Tuple[type, ...]]] = None,
226
- element_type: Optional[Union[type, Tuple[type, ...]]] = None,
227
- coerce_output_type_to: type = None,
228
- coerce_scalar_input: bool = False,
229
- sequence_name: str = None,
230
- ) -> Sequence[Any]:
231
- """Check whether an object is a sequence with optional check of element types.
232
-
233
- If `element_type` is supplied all elements are also checked against provided types.
234
-
235
- Parameters
236
- ----------
237
- input_seq : Any
238
- The input sequence to be validated.
239
- sequence_type : type or tuple[type], default=None
240
- The allowed sequence type that `seq` can be an instance of.
241
- element_type : type or tuple[type], default=None
242
- The allowed type(s) for elements of `seq`.
243
- coerce_output_type_to : sequence type
244
- The sequence type that the output sequence should be coerced to.
245
-
246
- - If None, then the output sequence is the same as input sequence.
247
- - If a sequence type (e.g., list, tuple) is provided then the output sequence
248
- is coerced to that type.
249
-
250
- coerce_scalar_input : bool, default=False
251
- Whether scalar input should be coerced to a sequence type prior to running
252
- the check. If True, a scalar input like will be coerced to a tuple containing
253
- a single scalar. To output a sequence type other than a tuple, set the
254
- `coerce_output_type_to` keyword to the desired sequence type (e.g., list).
255
- sequence_name : str, default=None
256
- Name of `input_seq` to use if error messages are raised.
257
-
258
- Returns
259
- -------
260
- Sequence
261
- The input sequence if has expected type.
262
-
263
- Raises
264
- ------
265
- TypeError :
266
- If `seq` is not instance of `sequence_type` or ``element_type is not None`` and
267
- all elements are not instances of `element_type`.
268
-
269
- Examples
270
- --------
271
- >>> from skbase.base import BaseEstimator, BaseObject
272
- >>> from skbase.validate import is_sequence
273
-
274
- >>> check_sequence([1, 2, 3])
275
- [1, 2, 3]
276
-
277
- Generators are not sequences so an error is raised
278
-
279
- >>> check_sequence((c for c in [1, 2, 3])) # doctest: +SKIP
280
-
281
- The check can require a certain type of sequence
282
-
283
- >>> check_sequence([1, 2, 3, 4], sequence_type=list)
284
- [1, 2, 3, 4]
285
-
286
- Expected to raise and error because the input is not a tuple
287
-
288
- >>> check_sequence([1, 2, 3, 4], sequence_type=tuple) # doctest: +SKIP
289
-
290
- It is also possible to check the type of sequence elements
291
-
292
- >>> check_sequence([1, 2, 3], element_type=int)
293
- [1, 2, 3]
294
- >>> check_sequence([1, 2, 3, 4], sequence_type=list, element_type=(int, float))
295
- [1, 2, 3, 4]
296
-
297
- The check also works with BaseObjects
298
-
299
- >>> check_sequence((BaseObject(), BaseEstimator()), element_type=BaseObject)
300
- (BaseObject(), BaseEstimator())
301
- """
302
- if coerce_scalar_input:
303
- if isinstance(sequence_type, tuple):
304
- # If multiple sequence types allowed then use first one
305
- input_seq = _scalar_to_seq(input_seq, sequence_type=sequence_type[0])
306
- else:
307
- input_seq = _scalar_to_seq(input_seq, sequence_type=sequence_type)
308
-
309
- is_valid_seqeunce = is_sequence(
310
- input_seq,
311
- sequence_type=sequence_type,
312
- element_type=element_type,
313
- )
314
- # Raise error is format is not expected.
315
- if not is_valid_seqeunce:
316
- name_str = "Input sequence" if sequence_name is None else f"`{sequence_name}`"
317
- if sequence_type is None:
318
- seq_str = "a sequence"
319
- else:
320
- sequence_type_ = _convert_scalar_seq_type_input_to_tuple(
321
- sequence_type,
322
- input_name="sequence_type",
323
- type_input_subclass=collections.abc.Sequence,
324
- )
325
- seq_str = _format_seq_to_str(
326
- sequence_type_, last_sep="or", remove_type_text=True
327
- )
328
-
329
- msg = f"Invalid sequence: {name_str} expected to be a {seq_str}."
330
-
331
- if element_type is not None:
332
- element_type_ = _convert_scalar_seq_type_input_to_tuple(
333
- element_type, input_name="element_type"
334
- )
335
- element_str = _format_seq_to_str(
336
- element_type_, last_sep="or", remove_type_text=True
337
- )
338
- msg = msg[:-1] + f" with elements of type {element_str}."
339
-
340
- raise TypeError(msg)
341
-
342
- if coerce_output_type_to is not None:
343
- return coerce_output_type_to(input_seq)
344
-
345
- return input_seq
1
+ #!/usr/bin/env python3 -u
2
+ # -*- coding: utf-8 -*-
3
+ # copyright: skbase developers, BSD-3-Clause License (see LICENSE file)
4
+ """Tools for validating types."""
5
+ import collections
6
+ import inspect
7
+ from typing import Any, List, Optional, Sequence, Tuple, Union
8
+
9
+ from skbase.utils._iter import _format_seq_to_str, _remove_type_text, _scalar_to_seq
10
+
11
+ __author__: List[str] = ["RNKuhns", "fkiraly"]
12
+ __all__: List[str] = ["check_sequence", "check_type", "is_sequence"]
13
+
14
+
15
+ def check_type(
16
+ input_: Any,
17
+ expected_type: type,
18
+ allow_none: bool = False,
19
+ input_name: Optional[str] = None,
20
+ use_subclass: bool = False,
21
+ ) -> Any:
22
+ """Check the input is the expected type.
23
+
24
+ Validates that the input is the type specified in `expected_type`, while optionally
25
+ allowing None values as well (if ``allow_none=True``). For flexibility,
26
+ the check can use ``issubclass`` instead of ``isinstance`` if ``use_subclass=True``.
27
+
28
+ Parameters
29
+ ----------
30
+ input_ : Any
31
+ The input to be type checked.
32
+ expected_type : type
33
+ The type that `input_` is expected to be.
34
+ allow_none : bool, default=False
35
+ Whether `input_` can be None in addition to being instance of `expected_type`.
36
+ input_name : str, default=None
37
+ The name to use when referring to `input_` in any raised error messages.
38
+ If None, then "input" is used as `input_name`.
39
+ use_subclass : bool, default=False
40
+ Whether to check the type using issubclass instead of isinstance.
41
+
42
+ - If True, then check uses issubclass.
43
+ - If False (default), then check uses isinstance.
44
+
45
+ Returns
46
+ -------
47
+ Any
48
+ The input object.
49
+
50
+ Raises
51
+ ------
52
+ TypeError
53
+ If input does match expected type using isinstance by default
54
+ or using issubclass in check if ``use_subclass=True``.
55
+
56
+ Examples
57
+ --------
58
+ >>> from skbase.base import BaseEstimator, BaseObject
59
+ >>> from skbase.validate import check_type
60
+ >>> check_type(7, expected_type=int)
61
+ 7
62
+ >>> check_type(7.2, expected_type=(int, float))
63
+ 7.2
64
+ >>> check_type(BaseEstimator(), BaseObject)
65
+ BaseEstimator()
66
+ >>> check_type(BaseEstimator, expected_type=BaseObject, use_subclass=True)
67
+ <class 'skbase.base._base.BaseEstimator'>
68
+
69
+ An error is raised if the input is not the expected type
70
+
71
+ >>> check_type(7, expected_type=str) # doctest: +SKIP
72
+ TypeError: `input` should be type <class 'str'>, but found <class 'str'>.
73
+ """
74
+ # process expected_type parameter
75
+ if not isinstance(expected_type, (type, tuple)):
76
+ msg = " ".join(
77
+ [
78
+ "`expected_type` should be type or tuple[type, ...],"
79
+ f"but found {_remove_type_text(expected_type)}."
80
+ ]
81
+ )
82
+ raise TypeError(msg)
83
+
84
+ # Assign default name to input_name parameter in case it is None
85
+ if input_name is None:
86
+ input_name = "input"
87
+
88
+ # Check the type of input_
89
+ type_check = issubclass if use_subclass else isinstance
90
+ if (allow_none and input_ is None) or type_check(input_, expected_type):
91
+ return input_
92
+ else:
93
+ chk_msg = "subclass type" if use_subclass else "be type"
94
+ expected_type_str = _remove_type_text(expected_type)
95
+ input_type_str = _remove_type_text(type(input_))
96
+ if allow_none:
97
+ type_msg = f"{expected_type_str} or None"
98
+ else:
99
+ type_msg = f"{expected_type_str}"
100
+ raise TypeError(
101
+ f"`{input_name}` should {chk_msg} {type_msg}, but found {input_type_str}."
102
+ )
103
+
104
+
105
+ def _convert_scalar_seq_type_input_to_tuple(
106
+ type_input: Optional[Union[type, Tuple[type, ...]]],
107
+ none_default: Optional[type] = None,
108
+ type_input_subclass: Optional[type] = None,
109
+ input_name: str = None,
110
+ ) -> Tuple[type, ...]:
111
+ """Convert input that is scalar or sequence of types to always be a tuple."""
112
+ if none_default is None:
113
+ none_default = collections.abc.Sequence
114
+
115
+ seq_output: Tuple[type, ...]
116
+ if type_input is None:
117
+ seq_output = (none_default,)
118
+ # if a sequence of types received as sequence_type, convert to tuple of types
119
+ elif isinstance(type_input, collections.abc.Sequence) and all(
120
+ isinstance(e, type) for e in type_input
121
+ ):
122
+ seq_output = tuple(type_input)
123
+ elif (isinstance(type_input, type) or inspect.isclass(type_input)) and (
124
+ type_input_subclass is None or issubclass(type_input, type_input_subclass)
125
+ ):
126
+ seq_output = (type_input,)
127
+ else:
128
+ name_str = "type_input" if input_name is None else input_name
129
+ raise TypeError(f"`{name_str}` should be a type or tuple of types.")
130
+
131
+ return seq_output
132
+
133
+
134
+ def is_sequence(
135
+ input_seq: Any,
136
+ sequence_type: Optional[Union[type, Tuple[type, ...]]] = None,
137
+ element_type: Optional[Union[type, Tuple[type, ...]]] = None,
138
+ ) -> bool:
139
+ """Indicate if an object is a sequence with optional check of element types.
140
+
141
+ If `element_type` is supplied all elements are also checked against provided types.
142
+
143
+ Parameters
144
+ ----------
145
+ input_seq : Any
146
+ The input sequence to be validated.
147
+ sequence_type : type or tuple[type, ...], default=None
148
+ The allowed sequence type(s) that `input_seq` can be an instance of.
149
+
150
+ - If None, then collections.abc.Sequence is used (all sequence types are valid)
151
+ - If `sequence_type` is a type or tuple of types, then only the specified
152
+ types are considered valid.
153
+
154
+ element_type : type or tuple[type], default=None
155
+ The allowed type(s) for elements of `input_seq`.
156
+
157
+ - If None, then the elements of `input_seq` are not checked when determining
158
+ if `input_seq` is a valid sequence.
159
+ - If `element_type` is a type or tuple of types, then the elements of
160
+ `input_seq` are checked to make sure they are all instances of
161
+ the supplied `element_type`.
162
+
163
+ Returns
164
+ -------
165
+ bool
166
+ Whether the input is a valid sequence based on the supplied `sequence_type`
167
+ and `element_type`.
168
+
169
+ Examples
170
+ --------
171
+ >>> from skbase.base import BaseEstimator, BaseObject
172
+ >>> from skbase.validate import is_sequence
173
+ >>> is_sequence([1, 2, 3])
174
+ True
175
+ >>> is_sequence(7)
176
+ False
177
+
178
+ Generators are not sequences
179
+
180
+ >>> is_sequence((c for c in [1, 2, 3]))
181
+ False
182
+
183
+ The expected sequence type can be included in the check
184
+
185
+ >>> is_sequence([1, 2, 3, 4], sequence_type=list)
186
+ True
187
+ >>> is_sequence([1, 2, 3, 4], sequence_type=tuple)
188
+ False
189
+
190
+ The type of the elements can also be checked
191
+
192
+ >>> is_sequence([1, 2, 3], element_type=int)
193
+ True
194
+ >>> is_sequence([1, 2, 3, 4], sequence_type=list, element_type=int)
195
+ True
196
+ >>> is_sequence([1, 2, 3, 4], sequence_type=list, element_type=float)
197
+ False
198
+ >>> is_sequence([1, 2, 3, 4], sequence_type=list, element_type=(int, float))
199
+ True
200
+ >>> is_sequence((BaseObject(), BaseEstimator()), element_type=BaseObject)
201
+ True
202
+ """
203
+ sequence_type_ = _convert_scalar_seq_type_input_to_tuple(
204
+ sequence_type,
205
+ input_name="sequence_type",
206
+ type_input_subclass=collections.abc.Sequence,
207
+ )
208
+
209
+ is_valid_sequence = isinstance(input_seq, sequence_type_)
210
+
211
+ # Optionally verify elements have correct types
212
+ if element_type is not None:
213
+ element_type_ = _convert_scalar_seq_type_input_to_tuple(
214
+ element_type, input_name="element_type"
215
+ )
216
+ element_types_okay = all(isinstance(e, element_type_) for e in input_seq)
217
+ if not element_types_okay:
218
+ is_valid_sequence = False
219
+
220
+ return is_valid_sequence
221
+
222
+
223
+ def check_sequence(
224
+ input_seq: Sequence[Any],
225
+ sequence_type: Optional[Union[type, Tuple[type, ...]]] = None,
226
+ element_type: Optional[Union[type, Tuple[type, ...]]] = None,
227
+ coerce_output_type_to: type = None,
228
+ coerce_scalar_input: bool = False,
229
+ sequence_name: str = None,
230
+ ) -> Sequence[Any]:
231
+ """Check whether an object is a sequence with optional check of element types.
232
+
233
+ If `element_type` is supplied all elements are also checked against provided types.
234
+
235
+ Parameters
236
+ ----------
237
+ input_seq : Any
238
+ The input sequence to be validated.
239
+ sequence_type : type or tuple[type], default=None
240
+ The allowed sequence type that `seq` can be an instance of.
241
+ element_type : type or tuple[type], default=None
242
+ The allowed type(s) for elements of `seq`.
243
+ coerce_output_type_to : sequence type
244
+ The sequence type that the output sequence should be coerced to.
245
+
246
+ - If None, then the output sequence is the same as input sequence.
247
+ - If a sequence type (e.g., list, tuple) is provided then the output sequence
248
+ is coerced to that type.
249
+
250
+ coerce_scalar_input : bool, default=False
251
+ Whether scalar input should be coerced to a sequence type prior to running
252
+ the check. If True, a scalar input like will be coerced to a tuple containing
253
+ a single scalar. To output a sequence type other than a tuple, set the
254
+ `coerce_output_type_to` keyword to the desired sequence type (e.g., list).
255
+ sequence_name : str, default=None
256
+ Name of `input_seq` to use if error messages are raised.
257
+
258
+ Returns
259
+ -------
260
+ Sequence
261
+ The input sequence if has expected type.
262
+
263
+ Raises
264
+ ------
265
+ TypeError :
266
+ If `seq` is not instance of `sequence_type` or ``element_type is not None`` and
267
+ all elements are not instances of `element_type`.
268
+
269
+ Examples
270
+ --------
271
+ >>> from skbase.base import BaseEstimator, BaseObject
272
+ >>> from skbase.validate import is_sequence
273
+
274
+ >>> check_sequence([1, 2, 3])
275
+ [1, 2, 3]
276
+
277
+ Generators are not sequences so an error is raised
278
+
279
+ >>> check_sequence((c for c in [1, 2, 3])) # doctest: +SKIP
280
+
281
+ The check can require a certain type of sequence
282
+
283
+ >>> check_sequence([1, 2, 3, 4], sequence_type=list)
284
+ [1, 2, 3, 4]
285
+
286
+ Expected to raise and error because the input is not a tuple
287
+
288
+ >>> check_sequence([1, 2, 3, 4], sequence_type=tuple) # doctest: +SKIP
289
+
290
+ It is also possible to check the type of sequence elements
291
+
292
+ >>> check_sequence([1, 2, 3], element_type=int)
293
+ [1, 2, 3]
294
+ >>> check_sequence([1, 2, 3, 4], sequence_type=list, element_type=(int, float))
295
+ [1, 2, 3, 4]
296
+
297
+ The check also works with BaseObjects
298
+
299
+ >>> check_sequence((BaseObject(), BaseEstimator()), element_type=BaseObject)
300
+ (BaseObject(), BaseEstimator())
301
+ """
302
+ if coerce_scalar_input:
303
+ if isinstance(sequence_type, tuple):
304
+ # If multiple sequence types allowed then use first one
305
+ input_seq = _scalar_to_seq(input_seq, sequence_type=sequence_type[0])
306
+ else:
307
+ input_seq = _scalar_to_seq(input_seq, sequence_type=sequence_type)
308
+
309
+ is_valid_seqeunce = is_sequence(
310
+ input_seq,
311
+ sequence_type=sequence_type,
312
+ element_type=element_type,
313
+ )
314
+ # Raise error is format is not expected.
315
+ if not is_valid_seqeunce:
316
+ name_str = "Input sequence" if sequence_name is None else f"`{sequence_name}`"
317
+ if sequence_type is None:
318
+ seq_str = "a sequence"
319
+ else:
320
+ sequence_type_ = _convert_scalar_seq_type_input_to_tuple(
321
+ sequence_type,
322
+ input_name="sequence_type",
323
+ type_input_subclass=collections.abc.Sequence,
324
+ )
325
+ seq_str = _format_seq_to_str(
326
+ sequence_type_, last_sep="or", remove_type_text=True
327
+ )
328
+
329
+ msg = f"Invalid sequence: {name_str} expected to be a {seq_str}."
330
+
331
+ if element_type is not None:
332
+ element_type_ = _convert_scalar_seq_type_input_to_tuple(
333
+ element_type, input_name="element_type"
334
+ )
335
+ element_str = _format_seq_to_str(
336
+ element_type_, last_sep="or", remove_type_text=True
337
+ )
338
+ msg = msg[:-1] + f" with elements of type {element_str}."
339
+
340
+ raise TypeError(msg)
341
+
342
+ if coerce_output_type_to is not None:
343
+ return coerce_output_type_to(input_seq)
344
+
345
+ return input_seq