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.
- docs/source/conf.py +299 -299
- {scikit_base-0.4.6.dist-info → scikit_base-0.5.1.dist-info}/LICENSE +29 -29
- {scikit_base-0.4.6.dist-info → scikit_base-0.5.1.dist-info}/METADATA +160 -159
- scikit_base-0.5.1.dist-info/RECORD +58 -0
- {scikit_base-0.4.6.dist-info → scikit_base-0.5.1.dist-info}/WHEEL +1 -1
- scikit_base-0.5.1.dist-info/top_level.txt +5 -0
- {scikit_base-0.4.6.dist-info → scikit_base-0.5.1.dist-info}/zip-safe +1 -1
- skbase/__init__.py +14 -14
- skbase/_exceptions.py +31 -31
- skbase/_nopytest_tests.py +35 -35
- skbase/base/__init__.py +20 -20
- skbase/base/_base.py +1249 -1249
- skbase/base/_meta.py +883 -871
- skbase/base/_pretty_printing/__init__.py +11 -11
- skbase/base/_pretty_printing/_object_html_repr.py +392 -392
- skbase/base/_pretty_printing/_pprint.py +412 -412
- skbase/base/_tagmanager.py +217 -217
- skbase/lookup/__init__.py +31 -31
- skbase/lookup/_lookup.py +1009 -1009
- skbase/lookup/tests/__init__.py +2 -2
- skbase/lookup/tests/test_lookup.py +991 -991
- skbase/testing/__init__.py +12 -12
- skbase/testing/test_all_objects.py +852 -856
- skbase/testing/utils/__init__.py +5 -5
- skbase/testing/utils/_conditional_fixtures.py +209 -209
- skbase/testing/utils/_dependencies.py +15 -15
- skbase/testing/utils/deep_equals.py +15 -15
- skbase/testing/utils/inspect.py +30 -30
- skbase/testing/utils/tests/__init__.py +2 -2
- skbase/testing/utils/tests/test_check_dependencies.py +49 -49
- skbase/testing/utils/tests/test_deep_equals.py +66 -66
- skbase/tests/__init__.py +2 -2
- skbase/tests/conftest.py +273 -273
- skbase/tests/mock_package/__init__.py +5 -5
- skbase/tests/mock_package/test_mock_package.py +74 -74
- skbase/tests/test_base.py +1202 -1202
- skbase/tests/test_baseestimator.py +130 -130
- skbase/tests/test_exceptions.py +23 -23
- skbase/tests/test_meta.py +170 -131
- skbase/utils/__init__.py +21 -21
- skbase/utils/_check.py +53 -53
- skbase/utils/_iter.py +238 -238
- skbase/utils/_nested_iter.py +180 -180
- skbase/utils/_utils.py +91 -91
- skbase/utils/deep_equals.py +358 -358
- skbase/utils/dependencies/__init__.py +11 -11
- skbase/utils/dependencies/_dependencies.py +253 -253
- skbase/utils/tests/__init__.py +4 -4
- skbase/utils/tests/test_check.py +24 -24
- skbase/utils/tests/test_iter.py +127 -127
- skbase/utils/tests/test_nested_iter.py +84 -84
- skbase/utils/tests/test_utils.py +37 -37
- skbase/validate/__init__.py +22 -22
- skbase/validate/_named_objects.py +403 -403
- skbase/validate/_types.py +345 -345
- skbase/validate/tests/__init__.py +2 -2
- skbase/validate/tests/test_iterable_named_objects.py +200 -200
- skbase/validate/tests/test_type_validations.py +370 -370
- scikit_base-0.4.6.dist-info/RECORD +0 -58
- 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
|