assertpy 2.35.0__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.
- assertpy/__init__.py +87 -0
- assertpy/_clustering.py +511 -0
- assertpy/_dangling.py +505 -0
- assertpy/_engine/__init__.py +1 -0
- assertpy/_engine/_builder_check_typing.py +1365 -0
- assertpy/_engine/_capable_typing.py +772 -0
- assertpy/_engine/_check_typing.py +753 -0
- assertpy/_engine/_compare.py +983 -0
- assertpy/_engine/_compat.py +38 -0
- assertpy/_engine/_contract.py +1926 -0
- assertpy/_engine/_diff.py +1058 -0
- assertpy/_engine/_equality.py +843 -0
- assertpy/_engine/_introspection.py +729 -0
- assertpy/_engine/_membership.py +522 -0
- assertpy/_engine/_mixin_base.py +258 -0
- assertpy/_engine/_negated_typing.py +2176 -0
- assertpy/_engine/_operations.py +75 -0
- assertpy/_engine/_ordering.py +884 -0
- assertpy/_engine/_pairing.py +70 -0
- assertpy/_engine/_path.py +158 -0
- assertpy/_engine/_poll_typing.py +6039 -0
- assertpy/_engine/_require.py +312 -0
- assertpy/_engine/_size.py +31 -0
- assertpy/_engine/_text.py +39 -0
- assertpy/_engine/_typing.py +1226 -0
- assertpy/_hints.py +1213 -0
- assertpy/_inline.py +484 -0
- assertpy/_matcher_impls.py +1876 -0
- assertpy/_passes.py +249 -0
- assertpy/_renamed.py +64 -0
- assertpy/_satisfies.py +835 -0
- assertpy/_snapshot_codec.py +290 -0
- assertpy/assertpy.py +2640 -0
- assertpy/async_assertions.py +864 -0
- assertpy/base.py +862 -0
- assertpy/behave_matchers.py +96 -0
- assertpy/bytes_mixin.py +158 -0
- assertpy/collection.py +528 -0
- assertpy/contains.py +1068 -0
- assertpy/dataframe.py +200 -0
- assertpy/date.py +327 -0
- assertpy/dict.py +249 -0
- assertpy/dynamic.py +222 -0
- assertpy/errors.py +782 -0
- assertpy/exception.py +838 -0
- assertpy/extracting.py +266 -0
- assertpy/file.py +324 -0
- assertpy/helpers.py +747 -0
- assertpy/http_mixin.py +218 -0
- assertpy/json_mixin.py +1265 -0
- assertpy/matchers.py +639 -0
- assertpy/numeric.py +696 -0
- assertpy/outcome.py +163 -0
- assertpy/py.typed +0 -0
- assertpy/pytest_plugin.py +1650 -0
- assertpy/snapshot.py +1031 -0
- assertpy/string.py +803 -0
- assertpy/warning.py +137 -0
- assertpy-2.35.0.dist-info/METADATA +324 -0
- assertpy-2.35.0.dist-info/RECORD +63 -0
- assertpy-2.35.0.dist-info/WHEEL +4 -0
- assertpy-2.35.0.dist-info/entry_points.txt +2 -0
- assertpy-2.35.0.dist-info/licenses/LICENSE +28 -0
assertpy/collection.py
ADDED
|
@@ -0,0 +1,528 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import collections.abc
|
|
4
|
+
import inspect
|
|
5
|
+
from typing import TYPE_CHECKING, Any, SupportsIndex, cast
|
|
6
|
+
|
|
7
|
+
from ._engine._introspection import is_mapping_like
|
|
8
|
+
from ._engine._membership import flattened_supersets, subset_faults
|
|
9
|
+
from ._engine._mixin_base import _MixinBase
|
|
10
|
+
from ._engine._ordering import (
|
|
11
|
+
UnorderableError,
|
|
12
|
+
first_out_of_order,
|
|
13
|
+
first_plainly_out_of_order,
|
|
14
|
+
plainly_sortable,
|
|
15
|
+
require_integer,
|
|
16
|
+
)
|
|
17
|
+
from ._engine._require import argument, refuse, require_type, sized_len, verdict
|
|
18
|
+
from ._hints import Roles
|
|
19
|
+
from ._satisfies import _warn_vacuous
|
|
20
|
+
from .contains import _one_not_found, _pair_why
|
|
21
|
+
from .errors import _capped, _capped_format
|
|
22
|
+
from .matchers import _is_matcher
|
|
23
|
+
|
|
24
|
+
if TYPE_CHECKING:
|
|
25
|
+
from collections.abc import Callable, Iterable, Sized
|
|
26
|
+
|
|
27
|
+
from ._engine._compat import Self
|
|
28
|
+
from ._engine._introspection import MappingLike
|
|
29
|
+
from .matchers import Matcher
|
|
30
|
+
|
|
31
|
+
__tracebackhide__ = True
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
class CollectionMixin(_MixinBase):
|
|
35
|
+
"""Collection assertions mixin."""
|
|
36
|
+
|
|
37
|
+
def _as_list(self) -> list[Any]:
|
|
38
|
+
"""Returns val as a list, raising TypeError if val is not iterable."""
|
|
39
|
+
require_type(self.val, collections.abc.Iterable, "iterable")
|
|
40
|
+
return list(self._walked())
|
|
41
|
+
|
|
42
|
+
def is_iterable(self) -> Self:
|
|
43
|
+
"""Asserts that val is iterable.
|
|
44
|
+
|
|
45
|
+
Examples:
|
|
46
|
+
Usage:
|
|
47
|
+
|
|
48
|
+
assert_that('foo').is_iterable()
|
|
49
|
+
assert_that(['a', 'b']).is_iterable()
|
|
50
|
+
assert_that((1, 2, 3)).is_iterable()
|
|
51
|
+
|
|
52
|
+
Returns:
|
|
53
|
+
AssertionBuilder: returns this instance to chain to the next assertion
|
|
54
|
+
|
|
55
|
+
Raises:
|
|
56
|
+
AssertionError: if val is **not** iterable
|
|
57
|
+
"""
|
|
58
|
+
if not isinstance(self.val, collections.abc.Iterable):
|
|
59
|
+
return self.error("Expected iterable, but was not.")
|
|
60
|
+
return self
|
|
61
|
+
|
|
62
|
+
def is_not_iterable(self) -> Self:
|
|
63
|
+
"""Asserts that val is not iterable.
|
|
64
|
+
|
|
65
|
+
Examples:
|
|
66
|
+
Usage:
|
|
67
|
+
|
|
68
|
+
assert_that(1).is_not_iterable()
|
|
69
|
+
assert_that(123.4).is_not_iterable()
|
|
70
|
+
assert_that(True).is_not_iterable()
|
|
71
|
+
assert_that(None).is_not_iterable()
|
|
72
|
+
|
|
73
|
+
Returns:
|
|
74
|
+
AssertionBuilder: returns this instance to chain to the next assertion
|
|
75
|
+
|
|
76
|
+
Raises:
|
|
77
|
+
AssertionError: if val **is** iterable
|
|
78
|
+
"""
|
|
79
|
+
if isinstance(self.val, collections.abc.Iterable):
|
|
80
|
+
return self.error("Expected not iterable, but was.")
|
|
81
|
+
return self
|
|
82
|
+
|
|
83
|
+
def is_subset_of(self, *supersets: object, allow_empty: bool = False) -> Self:
|
|
84
|
+
"""Asserts that val is iterable and a subset of the given superset (or supersets).
|
|
85
|
+
|
|
86
|
+
Args:
|
|
87
|
+
*supersets (object): the expected superset (or supersets)
|
|
88
|
+
|
|
89
|
+
Examples:
|
|
90
|
+
Usage:
|
|
91
|
+
|
|
92
|
+
assert_that('foo').is_subset_of('abcdefghijklmnopqrstuvwxyz')
|
|
93
|
+
assert_that(['a', 'b']).is_subset_of(['a', 'b', 'c'])
|
|
94
|
+
assert_that((1, 2, 3)).is_subset_of([1, 2, 3, 4])
|
|
95
|
+
assert_that({'a': 1, 'b': 2}).is_subset_of({'a': 1, 'b': 2, 'c': 3})
|
|
96
|
+
assert_that({'a', 'b'}).is_subset_of({'a', 'b', 'c'})
|
|
97
|
+
|
|
98
|
+
# or multiple supersets (as comma-separated args)
|
|
99
|
+
assert_that('aBc').is_subset_of('abc', 'ABC')
|
|
100
|
+
assert_that((1, 2, 3)).is_subset_of([1, 3, 5], [2, 4, 6])
|
|
101
|
+
|
|
102
|
+
assert_that({'a': 1, 'b': 2}).is_subset_of({'a': 1, 'c': 3}) # fails
|
|
103
|
+
# Expected <{'a': 1, 'b': 2}> to be subset of <{'a': 1, 'c': 3}>, but <{'b': 2}> was missing.
|
|
104
|
+
|
|
105
|
+
Returns:
|
|
106
|
+
AssertionBuilder: returns this instance to chain to the next assertion
|
|
107
|
+
|
|
108
|
+
Raises:
|
|
109
|
+
AssertionError: if val is **not** subset of given superset (or supersets)
|
|
110
|
+
|
|
111
|
+
Note:
|
|
112
|
+
Where one element of val is not found, the failure says what is nearest to it: the closest row among
|
|
113
|
+
the items of the superset, and on a line under the message why it is not there, where one fact about
|
|
114
|
+
it says so.
|
|
115
|
+
"""
|
|
116
|
+
require_type(self.val, collections.abc.Iterable, "iterable")
|
|
117
|
+
if len(supersets) == 0:
|
|
118
|
+
raise ValueError("one or more superset args must be given")
|
|
119
|
+
|
|
120
|
+
missing = []
|
|
121
|
+
if is_mapping_like(self.val):
|
|
122
|
+
# read whole first: a superset sharing this iterator leaves nothing, and a subset of nothing is anything
|
|
123
|
+
entries = [(key, self.val[key]) for key in self.val]
|
|
124
|
+
# each read once and kept apart: merging printed a value under a key no superset held it under
|
|
125
|
+
subject = dict(entries)
|
|
126
|
+
read = []
|
|
127
|
+
for superset_index, superset in enumerate(supersets):
|
|
128
|
+
self._require_dict_like(superset, check_values=False, name=f"arg #{superset_index + 1}")
|
|
129
|
+
mapping = cast("MappingLike", superset)
|
|
130
|
+
# a one-shot subject given as its own superset has nothing left to read, and is its own subset
|
|
131
|
+
read.append(subject if superset is self.val else {key: mapping[key] for key in mapping})
|
|
132
|
+
missing.extend(subset_faults(subject, read))
|
|
133
|
+
searched_in = read[0] if len(read) == 1 else read
|
|
134
|
+
if missing:
|
|
135
|
+
return self.error(
|
|
136
|
+
f"Expected <{_capped(self.val)}> to be subset of <{_capped_format(searched_in)}>, "
|
|
137
|
+
f"but {self._fmt_items(missing)} {'was' if len(missing) == 1 else 'were'} missing."
|
|
138
|
+
+ _pair_why(missing, read),
|
|
139
|
+
expected=searched_in,
|
|
140
|
+
)
|
|
141
|
+
if not entries:
|
|
142
|
+
_warn_vacuous(self, "is_subset_of", allow_empty)
|
|
143
|
+
else:
|
|
144
|
+
walked = list(self._walked())
|
|
145
|
+
# flattened once: a one-shot superset is drained by the first pass and reads empty to a second
|
|
146
|
+
collected = flattened_supersets(supersets)
|
|
147
|
+
# the same core the matcher uses: a bare `set()` called a value whose hash disagrees with `==` missing
|
|
148
|
+
missing.extend(subset_faults(walked, (collected,)))
|
|
149
|
+
try:
|
|
150
|
+
# for the message only: the failure has always shown the superset as a set where that was possible
|
|
151
|
+
superset_values: object = set(collected)
|
|
152
|
+
except TypeError:
|
|
153
|
+
superset_values = collected
|
|
154
|
+
if missing:
|
|
155
|
+
roles = Roles("the item missing", "an item of the superset")
|
|
156
|
+
return self.error(
|
|
157
|
+
f"Expected <{_capped(self.val)}> to be subset of {self._fmt_items(superset_values)}, "
|
|
158
|
+
f"but {self._fmt_items(missing)} {'was' if len(missing) == 1 else 'were'} missing."
|
|
159
|
+
+ _one_not_found(missing, collected, noun="item", item_is_actual=True, roles=roles),
|
|
160
|
+
expected=superset_values,
|
|
161
|
+
)
|
|
162
|
+
if not walked:
|
|
163
|
+
_warn_vacuous(self, "is_subset_of", allow_empty)
|
|
164
|
+
|
|
165
|
+
return self
|
|
166
|
+
|
|
167
|
+
def is_sorted(
|
|
168
|
+
self,
|
|
169
|
+
key: Callable[[Any], Any] = lambda item: item,
|
|
170
|
+
reverse: bool = False,
|
|
171
|
+
*,
|
|
172
|
+
allow_empty: bool = False,
|
|
173
|
+
) -> Self:
|
|
174
|
+
"""Asserts that val is iterable and is sorted.
|
|
175
|
+
|
|
176
|
+
Args:
|
|
177
|
+
key (function): the one-arg function to extract the sort comparison key. Defaults to
|
|
178
|
+
``lambda x: x`` to just compare items directly.
|
|
179
|
+
reverse (bool): if ``True``, then comparison key is reversed. Defaults to ``False``.
|
|
180
|
+
|
|
181
|
+
Examples:
|
|
182
|
+
Usage:
|
|
183
|
+
|
|
184
|
+
assert_that(['a', 'b', 'c']).is_sorted()
|
|
185
|
+
assert_that((1, 2, 3)).is_sorted()
|
|
186
|
+
|
|
187
|
+
# with a key function
|
|
188
|
+
assert_that('aBc').is_sorted(key=str.lower)
|
|
189
|
+
|
|
190
|
+
# reverse order
|
|
191
|
+
assert_that(['c', 'b', 'a']).is_sorted(reverse=True)
|
|
192
|
+
assert_that((3, 2, 1)).is_sorted(reverse=True)
|
|
193
|
+
|
|
194
|
+
assert_that((1, 2, 3, 4, -5, 6)).is_sorted() # fails
|
|
195
|
+
# Expected <(1, 2, 3, 4, -5, 6)> to be sorted, but subset <4, -5> at index 3 is not.
|
|
196
|
+
|
|
197
|
+
Returns:
|
|
198
|
+
AssertionBuilder: returns this instance to chain to the next assertion
|
|
199
|
+
|
|
200
|
+
Raises:
|
|
201
|
+
AssertionError: if val is **not** sorted
|
|
202
|
+
"""
|
|
203
|
+
require_type(self.val, collections.abc.Iterable, "iterable")
|
|
204
|
+
if not callable(key):
|
|
205
|
+
refuse(key, "callable", subject=argument("key"))
|
|
206
|
+
|
|
207
|
+
walked = 0
|
|
208
|
+
|
|
209
|
+
def _counted(items):
|
|
210
|
+
nonlocal walked
|
|
211
|
+
for item in items:
|
|
212
|
+
walked += 1
|
|
213
|
+
yield item
|
|
214
|
+
|
|
215
|
+
broken = None
|
|
216
|
+
try:
|
|
217
|
+
if key is _UNKEYED and plainly_sortable(self.val):
|
|
218
|
+
walked = len(self.val)
|
|
219
|
+
broken = first_plainly_out_of_order(self.val, reverse=reverse)
|
|
220
|
+
else:
|
|
221
|
+
broken = first_out_of_order(_counted(self._walked()), key=key, reverse=reverse)
|
|
222
|
+
except UnorderableError:
|
|
223
|
+
# reported about the collection: Python's own message is about the operator and names neither side
|
|
224
|
+
unorderable = True
|
|
225
|
+
else:
|
|
226
|
+
unorderable = False
|
|
227
|
+
if unorderable:
|
|
228
|
+
refuse(self.val, "a collection whose items can be ordered against each other")
|
|
229
|
+
if broken is not None:
|
|
230
|
+
index, earlier, later = broken
|
|
231
|
+
direction = " reverse" if reverse else ""
|
|
232
|
+
return self.error(
|
|
233
|
+
f"Expected <{_capped(self.val)}> to be sorted{direction}, "
|
|
234
|
+
f"but subset {self._fmt_items([earlier, later])} at index {index} is not."
|
|
235
|
+
)
|
|
236
|
+
if not walked:
|
|
237
|
+
_warn_vacuous(self, "is_sorted", allow_empty)
|
|
238
|
+
return self
|
|
239
|
+
|
|
240
|
+
def has_same_size_as(self, other: Sized) -> Self:
|
|
241
|
+
"""Asserts that val has the same length as other.
|
|
242
|
+
|
|
243
|
+
Args:
|
|
244
|
+
other (object): a sized object whose ``len()`` is compared with val's
|
|
245
|
+
|
|
246
|
+
Examples:
|
|
247
|
+
Usage:
|
|
248
|
+
|
|
249
|
+
assert_that([1, 2, 3]).has_same_size_as((4, 5, 6))
|
|
250
|
+
assert_that('foo').has_same_size_as([1, 2, 3])
|
|
251
|
+
|
|
252
|
+
Returns:
|
|
253
|
+
AssertionBuilder: returns this instance to chain to the next assertion
|
|
254
|
+
|
|
255
|
+
Raises:
|
|
256
|
+
AssertionError: if val and other do **not** have the same length
|
|
257
|
+
TypeError: if other is not a sized object
|
|
258
|
+
"""
|
|
259
|
+
# the subject first: reading the operand first let the operand's own `__len__` answer for both
|
|
260
|
+
actual_len = sized_len(self.val)
|
|
261
|
+
# `other` was left to `len()`, whose message is about the builtin and names neither operand
|
|
262
|
+
other_len = sized_len(other, subject=argument("other"))
|
|
263
|
+
if actual_len != other_len:
|
|
264
|
+
return self.error(
|
|
265
|
+
f"Expected <{_capped(self.val)}> to have same size as <{_capped_format(other)}>"
|
|
266
|
+
f" of length <{other_len}>, but was length <{actual_len}>.",
|
|
267
|
+
expected=other_len,
|
|
268
|
+
)
|
|
269
|
+
return self
|
|
270
|
+
|
|
271
|
+
def has_size_greater_than(self, size: SupportsIndex) -> Self:
|
|
272
|
+
"""Asserts that val has a length strictly greater than the given size.
|
|
273
|
+
|
|
274
|
+
Args:
|
|
275
|
+
size: the size val's length must exceed
|
|
276
|
+
|
|
277
|
+
Examples:
|
|
278
|
+
Usage:
|
|
279
|
+
|
|
280
|
+
assert_that([1, 2, 3]).has_size_greater_than(2)
|
|
281
|
+
assert_that('foo').has_size_greater_than(1)
|
|
282
|
+
assert_that({'a': 1, 'b': 2}).has_size_greater_than(1)
|
|
283
|
+
|
|
284
|
+
Returns:
|
|
285
|
+
AssertionBuilder: returns this instance to chain to the next assertion
|
|
286
|
+
|
|
287
|
+
Raises:
|
|
288
|
+
AssertionError: if val's length is **not** greater than the given size
|
|
289
|
+
TypeError: if the given arg is not an int
|
|
290
|
+
ValueError: if the given arg is negative
|
|
291
|
+
"""
|
|
292
|
+
bound = size if type(size) is int else require_integer(size, "size")
|
|
293
|
+
if bound < 0:
|
|
294
|
+
raise ValueError("given arg must be a positive int")
|
|
295
|
+
actual = sized_len(self.val)
|
|
296
|
+
if actual <= bound:
|
|
297
|
+
return self.error(
|
|
298
|
+
f"Expected <{_capped(self.val)}> to have size greater than <{size}>, but was <{actual}>.",
|
|
299
|
+
expected=size,
|
|
300
|
+
)
|
|
301
|
+
return self
|
|
302
|
+
|
|
303
|
+
def has_size_less_than(self, size: SupportsIndex) -> Self:
|
|
304
|
+
"""Asserts that val has a length strictly less than the given size.
|
|
305
|
+
|
|
306
|
+
Args:
|
|
307
|
+
size: the size val's length must stay under
|
|
308
|
+
|
|
309
|
+
Examples:
|
|
310
|
+
Usage:
|
|
311
|
+
|
|
312
|
+
assert_that([1, 2, 3]).has_size_less_than(4)
|
|
313
|
+
assert_that('foo').has_size_less_than(5)
|
|
314
|
+
assert_that({'a': 1, 'b': 2}).has_size_less_than(3)
|
|
315
|
+
|
|
316
|
+
Returns:
|
|
317
|
+
AssertionBuilder: returns this instance to chain to the next assertion
|
|
318
|
+
|
|
319
|
+
Raises:
|
|
320
|
+
AssertionError: if val's length is **not** less than the given size
|
|
321
|
+
TypeError: if the given arg is not an int
|
|
322
|
+
ValueError: if the given arg is negative
|
|
323
|
+
"""
|
|
324
|
+
bound = size if type(size) is int else require_integer(size, "size")
|
|
325
|
+
if bound < 0:
|
|
326
|
+
raise ValueError("given arg must be a positive int")
|
|
327
|
+
actual = sized_len(self.val)
|
|
328
|
+
if actual >= bound:
|
|
329
|
+
return self.error(
|
|
330
|
+
f"Expected <{_capped(self.val)}> to have size less than <{size}>, but was <{actual}>.", expected=size
|
|
331
|
+
)
|
|
332
|
+
return self
|
|
333
|
+
|
|
334
|
+
def has_size_between(self, low: SupportsIndex, high: SupportsIndex) -> Self:
|
|
335
|
+
"""Asserts that val has a length between low and high (both inclusive).
|
|
336
|
+
|
|
337
|
+
Args:
|
|
338
|
+
low: the inclusive lower size bound
|
|
339
|
+
high: the inclusive upper size bound
|
|
340
|
+
|
|
341
|
+
Examples:
|
|
342
|
+
Usage:
|
|
343
|
+
|
|
344
|
+
assert_that([1, 2, 3]).has_size_between(1, 5)
|
|
345
|
+
assert_that('foo').has_size_between(3, 3)
|
|
346
|
+
assert_that({'a': 1, 'b': 2}).has_size_between(0, 2)
|
|
347
|
+
|
|
348
|
+
Returns:
|
|
349
|
+
AssertionBuilder: returns this instance to chain to the next assertion
|
|
350
|
+
|
|
351
|
+
Raises:
|
|
352
|
+
AssertionError: if val's length is **not** between low and high
|
|
353
|
+
TypeError: if a given arg is not an int
|
|
354
|
+
ValueError: if a given arg is negative, or low is greater than high
|
|
355
|
+
"""
|
|
356
|
+
least = low if type(low) is int else require_integer(low, "low")
|
|
357
|
+
most = high if type(high) is int else require_integer(high, "high")
|
|
358
|
+
if least < 0 or most < 0:
|
|
359
|
+
raise ValueError("given args must be positive ints")
|
|
360
|
+
if least > most:
|
|
361
|
+
raise ValueError("given low arg must be less than given high arg")
|
|
362
|
+
if not least <= sized_len(self.val) <= most:
|
|
363
|
+
return self.error(
|
|
364
|
+
f"Expected <{_capped(self.val)}> to have size between <{low}> "
|
|
365
|
+
f"and <{high}>, but was <{sized_len(self.val)}>.",
|
|
366
|
+
expected=(low, high),
|
|
367
|
+
)
|
|
368
|
+
return self
|
|
369
|
+
|
|
370
|
+
def filtered_on(self, predicate: Matcher[Any] | Callable[[Any], bool]) -> Self:
|
|
371
|
+
"""Returns a new builder with elements matching the predicate.
|
|
372
|
+
|
|
373
|
+
Args:
|
|
374
|
+
predicate: callable or Matcher. If a Matcher, uses ``predicate.matches(item)``.
|
|
375
|
+
|
|
376
|
+
Examples:
|
|
377
|
+
Usage:
|
|
378
|
+
|
|
379
|
+
assert_that([1, -2, 3]).filtered_on(lambda x: x > 0).is_length(2)
|
|
380
|
+
assert_that(items).filtered_on(match.is_positive()).is_not_empty()
|
|
381
|
+
|
|
382
|
+
Returns:
|
|
383
|
+
AssertionBuilder: returns a new instance with the filtered list as val
|
|
384
|
+
"""
|
|
385
|
+
require_type(self.val, collections.abc.Iterable, "iterable")
|
|
386
|
+
# the protocol test the other matcher-taking methods use: a duck-typed matcher was called as a function
|
|
387
|
+
matches = predicate.matches if _is_matcher(predicate) else cast("Callable[..., object]", predicate)
|
|
388
|
+
# counted in the pass: a generator is spent by it, and asking after made an emptied source read as empty
|
|
389
|
+
seen = 0
|
|
390
|
+
filtered = []
|
|
391
|
+
for item in self._walked():
|
|
392
|
+
seen += 1
|
|
393
|
+
if verdict(matches(item)):
|
|
394
|
+
filtered.append(item)
|
|
395
|
+
return self.builder(
|
|
396
|
+
filtered,
|
|
397
|
+
self.description,
|
|
398
|
+
self.kind,
|
|
399
|
+
logger=self.logger,
|
|
400
|
+
origin=f"filtered_on() kept {len(filtered)} of {seen} items",
|
|
401
|
+
)
|
|
402
|
+
|
|
403
|
+
def mapped(self, func: Callable[[Any], Any]) -> Self:
|
|
404
|
+
"""Returns a new builder with each element transformed by func.
|
|
405
|
+
|
|
406
|
+
Args:
|
|
407
|
+
func: callable applied to each element.
|
|
408
|
+
|
|
409
|
+
Examples:
|
|
410
|
+
Usage:
|
|
411
|
+
|
|
412
|
+
assert_that(["a", "b"]).mapped(str.upper).contains("A")
|
|
413
|
+
|
|
414
|
+
Returns:
|
|
415
|
+
AssertionBuilder: returns a new instance with the mapped list as val
|
|
416
|
+
"""
|
|
417
|
+
require_type(self.val, collections.abc.Iterable, "iterable")
|
|
418
|
+
mapped = [func(item) for item in self._walked()]
|
|
419
|
+
return self.builder(mapped, self.description, self.kind, logger=self.logger)
|
|
420
|
+
|
|
421
|
+
def flat_mapped(self, func: Callable[[Any], Iterable[Any]]) -> Self:
|
|
422
|
+
"""Returns a new builder with each element expanded and flattened by func.
|
|
423
|
+
|
|
424
|
+
Args:
|
|
425
|
+
func: callable returning an iterable for each element.
|
|
426
|
+
|
|
427
|
+
Examples:
|
|
428
|
+
Usage:
|
|
429
|
+
|
|
430
|
+
assert_that(["ab", "cd"]).flat_mapped(list).contains("a", "c")
|
|
431
|
+
|
|
432
|
+
Returns:
|
|
433
|
+
AssertionBuilder: returns a new instance with the flattened list as val
|
|
434
|
+
"""
|
|
435
|
+
require_type(self.val, collections.abc.Iterable, "iterable")
|
|
436
|
+
walked, flat = self._walked(), []
|
|
437
|
+
for item in walked:
|
|
438
|
+
made = func(item)
|
|
439
|
+
# the value itself handed back is read as every link reads it: taken from under them, it left the
|
|
440
|
+
# next link the first item alone
|
|
441
|
+
flat.extend(walked if made is self.val else made)
|
|
442
|
+
return self.builder(flat, self.description, self.kind, logger=self.logger)
|
|
443
|
+
|
|
444
|
+
def first(self) -> Self:
|
|
445
|
+
"""Returns a new builder with the first element of val.
|
|
446
|
+
|
|
447
|
+
Examples:
|
|
448
|
+
Usage:
|
|
449
|
+
|
|
450
|
+
assert_that([10, 20, 30]).first().is_equal_to(10)
|
|
451
|
+
|
|
452
|
+
Returns:
|
|
453
|
+
AssertionBuilder: returns a new instance with the first element as val
|
|
454
|
+
|
|
455
|
+
Raises:
|
|
456
|
+
ValueError: if val is empty
|
|
457
|
+
"""
|
|
458
|
+
items = self._as_list()
|
|
459
|
+
if not items:
|
|
460
|
+
raise ValueError("Expected non-empty iterable, but was empty.")
|
|
461
|
+
return self.builder(items[0], self.description, self.kind, logger=self.logger)
|
|
462
|
+
|
|
463
|
+
def last(self) -> Self:
|
|
464
|
+
"""Returns a new builder with the last element of val.
|
|
465
|
+
|
|
466
|
+
Examples:
|
|
467
|
+
Usage:
|
|
468
|
+
|
|
469
|
+
assert_that([10, 20, 30]).last().is_equal_to(30)
|
|
470
|
+
|
|
471
|
+
Returns:
|
|
472
|
+
AssertionBuilder: returns a new instance with the last element as val
|
|
473
|
+
|
|
474
|
+
Raises:
|
|
475
|
+
ValueError: if val is empty
|
|
476
|
+
"""
|
|
477
|
+
items = self._as_list()
|
|
478
|
+
if not items:
|
|
479
|
+
raise ValueError("Expected non-empty iterable, but was empty.")
|
|
480
|
+
return self.builder(items[-1], self.description, self.kind, logger=self.logger)
|
|
481
|
+
|
|
482
|
+
def element(self, index: SupportsIndex) -> Self:
|
|
483
|
+
"""Returns a new builder with the element at the given index.
|
|
484
|
+
|
|
485
|
+
Args:
|
|
486
|
+
index: zero-based index.
|
|
487
|
+
|
|
488
|
+
Examples:
|
|
489
|
+
Usage:
|
|
490
|
+
|
|
491
|
+
assert_that([10, 20, 30]).element(1).is_equal_to(20)
|
|
492
|
+
|
|
493
|
+
Returns:
|
|
494
|
+
AssertionBuilder: returns a new instance with the selected element as val
|
|
495
|
+
|
|
496
|
+
Raises:
|
|
497
|
+
IndexError: if index is out of range
|
|
498
|
+
"""
|
|
499
|
+
position = index if type(index) is int else require_integer(index, "index")
|
|
500
|
+
items = self._as_list()
|
|
501
|
+
if position < 0 or position >= len(items):
|
|
502
|
+
raise IndexError(f"Expected index {index} to be in range [0, {len(items)}), but was out of range.")
|
|
503
|
+
return self.builder(items[position], self.description, self.kind, logger=self.logger)
|
|
504
|
+
|
|
505
|
+
def single(self) -> Self:
|
|
506
|
+
"""Returns a new builder with the only element of val.
|
|
507
|
+
|
|
508
|
+
Examples:
|
|
509
|
+
Usage:
|
|
510
|
+
|
|
511
|
+
assert_that([42]).single().is_equal_to(42)
|
|
512
|
+
|
|
513
|
+
Returns:
|
|
514
|
+
AssertionBuilder: returns a new instance with the single element as val
|
|
515
|
+
|
|
516
|
+
Raises:
|
|
517
|
+
ValueError: if val is empty or has more than one element
|
|
518
|
+
"""
|
|
519
|
+
items = self._as_list()
|
|
520
|
+
if not items:
|
|
521
|
+
raise ValueError("Expected iterable with single element, but was empty.")
|
|
522
|
+
if len(items) > 1:
|
|
523
|
+
raise ValueError(f"Expected iterable with single element, but had {len(items)} elements.")
|
|
524
|
+
return self.builder(items[0], self.description, self.kind, logger=self.logger)
|
|
525
|
+
|
|
526
|
+
|
|
527
|
+
_UNKEYED = inspect.signature(CollectionMixin.is_sorted).parameters["key"].default
|
|
528
|
+
"""`is_sorted` without a `key`, which asks nothing of anybody's code and so may compare plain items in C."""
|