assertpy2 2.0.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.
assertpy2/base.py ADDED
@@ -0,0 +1,435 @@
1
+ # Copyright (c) 2015-2019, Activision Publishing, Inc.
2
+ # All rights reserved.
3
+ #
4
+ # Redistribution and use in source and binary forms, with or without modification,
5
+ # are permitted provided that the following conditions are met:
6
+ #
7
+ # 1. Redistributions of source code must retain the above copyright notice, this
8
+ # list of conditions and the following disclaimer.
9
+ #
10
+ # 2. Redistributions in binary form must reproduce the above copyright notice,
11
+ # this list of conditions and the following disclaimer in the documentation
12
+ # and/or other materials provided with the distribution.
13
+ #
14
+ # 3. Neither the name of the copyright holder nor the names of its contributors
15
+ # may be used to endorse or promote products derived from this software without
16
+ # specific prior written permission.
17
+ #
18
+ # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
19
+ # ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
20
+ # WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
21
+ # DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR
22
+ # ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
23
+ # (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
24
+ # LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
25
+ # ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
26
+ # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
27
+ # SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
28
+
29
+ from __future__ import annotations
30
+
31
+ from typing import TYPE_CHECKING
32
+
33
+ if TYPE_CHECKING:
34
+ from typing_extensions import Self
35
+
36
+ __tracebackhide__ = True
37
+
38
+
39
+ class BaseMixin:
40
+ """Base mixin."""
41
+
42
+ def described_as(self, description) -> Self:
43
+ """Describes the assertion. On failure, the description is included in the error message.
44
+
45
+ This is not an assertion itself. But if the any of the following chained assertions fail,
46
+ the description will be included in addition to the regular error message.
47
+
48
+ Args:
49
+ description: the error message description
50
+
51
+ Examples:
52
+ Usage::
53
+
54
+ assert_that(1).described_as('error msg desc').is_equal_to(2) # fails
55
+ # [error msg desc] Expected <1> to be equal to <2>, but was not.
56
+
57
+ Returns:
58
+ AssertionBuilder: returns this instance to chain to the next assertion
59
+ """
60
+ self.description = str(description)
61
+ return self
62
+
63
+ def is_equal_to(self, other, **kwargs) -> Self:
64
+ """Asserts that val is equal to other.
65
+
66
+ Checks actual is equal to expected using the ``==`` operator. When val is *dict-like*,
67
+ optionally ignore or include keys when checking equality.
68
+
69
+ Args:
70
+ other: the expected value
71
+ **kwargs: see below
72
+
73
+ Keyword Args:
74
+ ignore: the dict key (or list of keys) to ignore
75
+ include: the dict key (of list of keys) to include
76
+
77
+ Examples:
78
+ Usage::
79
+
80
+ assert_that(1 + 2).is_equal_to(3)
81
+ assert_that('foo').is_equal_to('foo')
82
+ assert_that(123).is_equal_to(123)
83
+ assert_that(123.4).is_equal_to(123.4)
84
+ assert_that(['a', 'b']).is_equal_to(['a', 'b'])
85
+ assert_that((1, 2, 3)).is_equal_to((1, 2, 3))
86
+ assert_that({'a': 1, 'b': 2}).is_equal_to({'a': 1, 'b': 2})
87
+ assert_that({'a', 'b'}).is_equal_to({'a', 'b'})
88
+
89
+ When the val is *dict-like*, keys can optionally be *ignored* when checking equality::
90
+
91
+ # ignore a single key
92
+ assert_that({'a': 1, 'b': 2}).is_equal_to({'a': 1}, ignore='b')
93
+
94
+ # ignore multiple keys
95
+ assert_that({'a': 1, 'b': 2, 'c': 3}).is_equal_to({'a': 1}, ignore=['b', 'c'])
96
+
97
+ # ignore nested keys
98
+ assert_that({'a': {'b': 2, 'c': 3, 'd': 4}}).is_equal_to({'a': {'d': 4}}, ignore=[('a', 'b'), ('a', 'c')])
99
+
100
+ When the val is *dict-like*, only certain keys can be *included* when checking equality::
101
+
102
+ # include a single key
103
+ assert_that({'a': 1, 'b': 2}).is_equal_to({'a': 1}, include='a')
104
+
105
+ # include multiple keys
106
+ assert_that({'a': 1, 'b': 2, 'c': 3}).is_equal_to({'a': 1, 'b': 2}, include=['a', 'b'])
107
+
108
+ Failure produces a nice error message::
109
+
110
+ assert_that(1).is_equal_to(2) # fails
111
+ # Expected <1> to be equal to <2>, but was not.
112
+
113
+ Returns:
114
+ AssertionBuilder: returns this instance to chain to the next assertion
115
+
116
+ Raises:
117
+ AssertionError: if actual is **not** equal to expected
118
+
119
+ Tip:
120
+ Using :meth:`is_equal_to` with a ``float`` val is just asking for trouble. Instead, you'll
121
+ always want to use *fuzzy* numeric assertions like :meth:`~assertpy.numeric.NumericMixin.is_close_to`
122
+ or :meth:`~assertpy.numeric.NumericMixin.is_between`.
123
+
124
+ See Also:
125
+ :meth:`~assertpy.string.StringMixin.is_equal_to_ignoring_case` - for case-insensitive string equality
126
+ """
127
+ if self._check_dict_like(self.val, check_values=False, return_as_bool=True) and self._check_dict_like(
128
+ other, check_values=False, return_as_bool=True
129
+ ):
130
+ if self._dict_not_equal(self.val, other, ignore=kwargs.get("ignore"), include=kwargs.get("include")):
131
+ self._dict_err(self.val, other, ignore=kwargs.get("ignore"), include=kwargs.get("include"))
132
+ else:
133
+ if self.val != other:
134
+ return self.error("Expected <%s> to be equal to <%s>, but was not." % (self.val, other))
135
+ return self
136
+
137
+ def is_not_equal_to(self, other) -> Self:
138
+ """Asserts that val is not equal to other.
139
+
140
+ Checks actual is not equal to expected using the ``!=`` operator.
141
+
142
+ Args:
143
+ other: the expected value
144
+
145
+ Examples:
146
+ Usage::
147
+
148
+ assert_that(1 + 2).is_not_equal_to(4)
149
+ assert_that('foo').is_not_equal_to('bar')
150
+ assert_that(123).is_not_equal_to(456)
151
+ assert_that(123.4).is_not_equal_to(567.8)
152
+ assert_that(['a', 'b']).is_not_equal_to(['c', 'd'])
153
+ assert_that((1, 2, 3)).is_not_equal_to((1, 2, 4))
154
+ assert_that({'a': 1, 'b': 2}).is_not_equal_to({'a': 1, 'b': 3})
155
+ assert_that({'a', 'b'}).is_not_equal_to({'a', 'x'})
156
+
157
+ Returns:
158
+ AssertionBuilder: returns this instance to chain to the next assertion
159
+
160
+ Raises:
161
+ AssertionError: if actual **is** equal to expected
162
+ """
163
+ if self.val == other:
164
+ return self.error("Expected <%s> to be not equal to <%s>, but was." % (self.val, other))
165
+ return self
166
+
167
+ def is_same_as(self, other) -> Self:
168
+ """Asserts that val is identical to other.
169
+
170
+ Checks actual is identical to expected using the ``is`` operator.
171
+
172
+ Args:
173
+ other: the expected value
174
+
175
+ Examples:
176
+ Basic types are identical::
177
+
178
+ assert_that(1).is_same_as(1)
179
+ assert_that('foo').is_same_as('foo')
180
+ assert_that(123.4).is_same_as(123.4)
181
+
182
+ As are immutables like ``tuple``::
183
+
184
+ assert_that((1, 2, 3)).is_same_as((1, 2, 3))
185
+
186
+ But mutable collections like ``list``, ``dict``, and ``set`` are not::
187
+
188
+ # these all fail...
189
+ assert_that(['a', 'b']).is_same_as(['a', 'b']) # fails
190
+ assert_that({'a': 1, 'b': 2}).is_same_as({'a': 1, 'b': 2}) # fails
191
+ assert_that({'a', 'b'}).is_same_as({'a', 'b'}) # fails
192
+
193
+ Unless they are the same object::
194
+
195
+ x = {'a': 1, 'b': 2}
196
+ y = x
197
+ assert_that(x).is_same_as(y)
198
+
199
+ Returns:
200
+ AssertionBuilder: returns this instance to chain to the next assertion
201
+
202
+ Raises:
203
+ AssertionError: if actual is **not** identical to expected
204
+ """
205
+ if self.val is not other:
206
+ return self.error("Expected <%s> to be identical to <%s>, but was not." % (self.val, other))
207
+ return self
208
+
209
+ def is_not_same_as(self, other) -> Self:
210
+ """Asserts that val is not identical to other.
211
+
212
+ Checks actual is not identical to expected using the ``is`` operator.
213
+
214
+ Args:
215
+ other: the expected value
216
+
217
+ Examples:
218
+ Usage::
219
+
220
+ assert_that(1).is_not_same_as(2)
221
+ assert_that('foo').is_not_same_as('bar')
222
+ assert_that(123.4).is_not_same_as(567.8)
223
+ assert_that((1, 2, 3)).is_not_same_as((1, 2, 4))
224
+
225
+ # mutable collections, even if equal, are not identical...
226
+ assert_that(['a', 'b']).is_not_same_as(['a', 'b'])
227
+ assert_that({'a': 1, 'b': 2}).is_not_same_as({'a': 1, 'b': 2})
228
+ assert_that({'a', 'b'}).is_not_same_as({'a', 'b'})
229
+
230
+ Returns:
231
+ AssertionBuilder: returns this instance to chain to the next assertion
232
+
233
+ Raises:
234
+ AssertionError: if actual **is** identical to expected
235
+ """
236
+ if self.val is other:
237
+ return self.error("Expected <%s> to be not identical to <%s>, but was." % (self.val, other))
238
+ return self
239
+
240
+ def is_true(self) -> Self:
241
+ """Asserts that val is true.
242
+
243
+ Examples:
244
+ Usage::
245
+
246
+ assert_that(True).is_true()
247
+ assert_that(1).is_true()
248
+ assert_that('foo').is_true()
249
+ assert_that(1.0).is_true()
250
+ assert_that(['a', 'b']).is_true()
251
+ assert_that((1, 2, 3)).is_true()
252
+ assert_that({'a': 1, 'b': 2}).is_true()
253
+ assert_that({'a', 'b'}).is_true()
254
+
255
+ Returns:
256
+ AssertionBuilder: returns this instance to chain to the next assertion
257
+
258
+ Raises:
259
+ AssertionError: if val **is** false
260
+ """
261
+ if not self.val:
262
+ return self.error("Expected <%s> to be <True>, but was not." % self.val)
263
+ return self
264
+
265
+ def is_false(self) -> Self:
266
+ """Asserts that val is false.
267
+
268
+ Examples:
269
+ Usage::
270
+
271
+ assert_that(False).is_false()
272
+ assert_that(0).is_false()
273
+ assert_that('').is_false()
274
+ assert_that(0.0).is_false()
275
+ assert_that([]).is_false()
276
+ assert_that(()).is_false()
277
+ assert_that({}).is_false()
278
+ assert_that(set()).is_false()
279
+
280
+ Returns:
281
+ AssertionBuilder: returns this instance to chain to the next assertion
282
+
283
+ Raises:
284
+ AssertionError: if val **is** true
285
+ """
286
+ if self.val:
287
+ return self.error("Expected <%s> to be <False>, but was not." % self.val)
288
+ return self
289
+
290
+ def is_none(self) -> Self:
291
+ """Asserts that val is none.
292
+
293
+ Examples:
294
+ Usage::
295
+
296
+ assert_that(None).is_none()
297
+ assert_that(print('hello world')).is_none()
298
+
299
+ Returns:
300
+ AssertionBuilder: returns this instance to chain to the next assertion
301
+
302
+ Raises:
303
+ AssertionError: if val is **not** none
304
+ """
305
+ if self.val is not None:
306
+ return self.error("Expected <%s> to be <None>, but was not." % self.val)
307
+ return self
308
+
309
+ def is_not_none(self) -> Self:
310
+ """Asserts that val is not none.
311
+
312
+ Examples:
313
+ Usage::
314
+
315
+ assert_that(0).is_not_none()
316
+ assert_that('foo').is_not_none()
317
+ assert_that(False).is_not_none()
318
+
319
+ Returns:
320
+ AssertionBuilder: returns this instance to chain to the next assertion
321
+
322
+ Raises:
323
+ AssertionError: if val **is** none
324
+ """
325
+ if self.val is None:
326
+ return self.error("Expected not <None>, but was.")
327
+ return self
328
+
329
+ def _type(self, val):
330
+ if hasattr(val, "__name__"):
331
+ return val.__name__
332
+ return val.__class__.__name__
333
+
334
+ def is_type_of(self, some_type) -> Self:
335
+ """Asserts that val is of the given type.
336
+
337
+ Args:
338
+ some_type (type): the expected type
339
+
340
+ Examples:
341
+ Usage::
342
+
343
+ assert_that(1).is_type_of(int)
344
+ assert_that('foo').is_type_of(str)
345
+ assert_that(123.4).is_type_of(float)
346
+ assert_that(['a', 'b']).is_type_of(list)
347
+ assert_that((1, 2, 3)).is_type_of(tuple)
348
+ assert_that({'a': 1, 'b': 2}).is_type_of(dict)
349
+ assert_that({'a', 'b'}).is_type_of(set)
350
+ assert_that(True).is_type_of(bool)
351
+
352
+ Returns:
353
+ AssertionBuilder: returns this instance to chain to the next assertion
354
+
355
+ Raises:
356
+ AssertionError: if val is **not** of the given type
357
+ """
358
+ if type(some_type) is not type and not issubclass(type(some_type), type):
359
+ raise TypeError("given arg must be a type")
360
+ if type(self.val) is not some_type:
361
+ t = self._type(self.val)
362
+ return self.error("Expected <%s:%s> to be of type <%s>, but was not." % (self.val, t, some_type.__name__))
363
+ return self
364
+
365
+ def is_instance_of(self, some_class) -> Self:
366
+ """Asserts that val is an instance of the given class.
367
+
368
+ Args:
369
+ some_class: the expected class
370
+
371
+ Examples:
372
+ Usage::
373
+
374
+ assert_that(1).is_instance_of(int)
375
+ assert_that('foo').is_instance_of(str)
376
+ assert_that(123.4).is_instance_of(float)
377
+ assert_that(['a', 'b']).is_instance_of(list)
378
+ assert_that((1, 2, 3)).is_instance_of(tuple)
379
+ assert_that({'a': 1, 'b': 2}).is_instance_of(dict)
380
+ assert_that({'a', 'b'}).is_instance_of(set)
381
+ assert_that(True).is_instance_of(bool)
382
+
383
+ With a user-defined class::
384
+
385
+ class Foo: pass
386
+ f = Foo()
387
+ assert_that(f).is_instance_of(Foo)
388
+ assert_that(f).is_instance_of(object)
389
+
390
+ Returns:
391
+ AssertionBuilder: returns this instance to chain to the next assertion
392
+
393
+ Raises:
394
+ AssertionError: if val is **not** an instance of the given class
395
+ """
396
+ try:
397
+ if not isinstance(self.val, some_class):
398
+ t = self._type(self.val)
399
+ return self.error(
400
+ "Expected <%s:%s> to be instance of class <%s>, but was not." % (self.val, t, some_class.__name__)
401
+ )
402
+ except TypeError:
403
+ raise TypeError("given arg must be a class") from None
404
+ return self
405
+
406
+ def is_length(self, length) -> Self:
407
+ """Asserts that val is the given length.
408
+
409
+ Checks val is the given length using the ``len()`` built-in.
410
+
411
+ Args:
412
+ length (int): the expected length
413
+
414
+ Examples:
415
+ Usage::
416
+
417
+ assert_that('foo').is_length(3)
418
+ assert_that(['a', 'b']).is_length(2)
419
+ assert_that((1, 2, 3)).is_length(3)
420
+ assert_that({'a': 1, 'b': 2}).is_length(2)
421
+ assert_that({'a', 'b'}).is_length(2)
422
+
423
+ Returns:
424
+ AssertionBuilder: returns this instance to chain to the next assertion
425
+
426
+ Raises:
427
+ AssertionError: if val is **not** the given length
428
+ """
429
+ if type(length) is not int:
430
+ raise TypeError("given arg must be an int")
431
+ if length < 0:
432
+ raise ValueError("given arg must be a positive int")
433
+ if len(self.val) != length:
434
+ return self.error("Expected <%s> to be of length <%d>, but was <%d>." % (self.val, length, len(self.val)))
435
+ return self
@@ -0,0 +1,217 @@
1
+ # Copyright (c) 2015-2019, Activision Publishing, Inc.
2
+ # All rights reserved.
3
+ #
4
+ # Redistribution and use in source and binary forms, with or without modification,
5
+ # are permitted provided that the following conditions are met:
6
+ #
7
+ # 1. Redistributions of source code must retain the above copyright notice, this
8
+ # list of conditions and the following disclaimer.
9
+ #
10
+ # 2. Redistributions in binary form must reproduce the above copyright notice,
11
+ # this list of conditions and the following disclaimer in the documentation
12
+ # and/or other materials provided with the distribution.
13
+ #
14
+ # 3. Neither the name of the copyright holder nor the names of its contributors
15
+ # may be used to endorse or promote products derived from this software without
16
+ # specific prior written permission.
17
+ #
18
+ # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
19
+ # ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
20
+ # WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
21
+ # DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR
22
+ # ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
23
+ # (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
24
+ # LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
25
+ # ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
26
+ # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
27
+ # SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
28
+
29
+ from __future__ import annotations
30
+
31
+ import collections
32
+ from typing import TYPE_CHECKING
33
+
34
+ if TYPE_CHECKING:
35
+ from typing_extensions import Self
36
+
37
+ __tracebackhide__ = True
38
+
39
+
40
+ class CollectionMixin:
41
+ """Collection assertions mixin."""
42
+
43
+ def is_iterable(self) -> Self:
44
+ """Asserts that val is iterable.
45
+
46
+ Examples:
47
+ Usage::
48
+
49
+ assert_that('foo').is_iterable()
50
+ assert_that(['a', 'b']).is_iterable()
51
+ assert_that((1, 2, 3)).is_iterable()
52
+
53
+ Returns:
54
+ AssertionBuilder: returns this instance to chain to the next assertion
55
+
56
+ Raises:
57
+ AssertionError: if val is **not** iterable
58
+ """
59
+ if not isinstance(self.val, collections.abc.Iterable):
60
+ return self.error("Expected iterable, but was not.")
61
+ return self
62
+
63
+ def is_not_iterable(self) -> Self:
64
+ """Asserts that val is not iterable.
65
+
66
+ Examples:
67
+ Usage::
68
+
69
+ assert_that(1).is_not_iterable()
70
+ assert_that(123.4).is_not_iterable()
71
+ assert_that(True).is_not_iterable()
72
+ assert_that(None).is_not_iterable()
73
+
74
+ Returns:
75
+ AssertionBuilder: returns this instance to chain to the next assertion
76
+
77
+ Raises:
78
+ AssertionError: if val **is** iterable
79
+ """
80
+ if isinstance(self.val, collections.abc.Iterable):
81
+ return self.error("Expected not iterable, but was.")
82
+ return self
83
+
84
+ def is_subset_of(self, *supersets) -> Self:
85
+ """Asserts that val is iterable and a subset of the given superset (or supersets).
86
+
87
+ Args:
88
+ *supersets: the expected superset (or supersets)
89
+
90
+ Examples:
91
+ Usage::
92
+
93
+ assert_that('foo').is_subset_of('abcdefghijklmnopqrstuvwxyz')
94
+ assert_that(['a', 'b']).is_subset_of(['a', 'b', 'c'])
95
+ assert_that((1, 2, 3)).is_subset_of([1, 2, 3, 4])
96
+ assert_that({'a': 1, 'b': 2}).is_subset_of({'a': 1, 'b': 2, 'c': 3})
97
+ assert_that({'a', 'b'}).is_subset_of({'a', 'b', 'c'})
98
+
99
+ # or multiple supersets (as comma-separated args)
100
+ assert_that('aBc').is_subset_of('abc', 'ABC')
101
+ assert_that((1, 2, 3)).is_subset_of([1, 3, 5], [2, 4, 6])
102
+
103
+ assert_that({'a': 1, 'b': 2}).is_subset_of({'a': 1, 'c': 3}) # fails
104
+ # Expected <{'a': 1, 'b': 2}> to be subset of <{'a': 1, 'c': 3}>, but <{'b': 2}> was missing.
105
+
106
+ Returns:
107
+ AssertionBuilder: returns this instance to chain to the next assertion
108
+
109
+ Raises:
110
+ AssertionError: if val is **not** subset of given superset (or supersets)
111
+ """
112
+ if not isinstance(self.val, collections.abc.Iterable):
113
+ raise TypeError("val is not iterable")
114
+ if len(supersets) == 0:
115
+ raise ValueError("one or more superset args must be given")
116
+
117
+ missing = []
118
+ if hasattr(self.val, "keys") and callable(self.val.keys) and hasattr(self.val, "__getitem__"):
119
+ # flatten superset dicts
120
+ superdict = {}
121
+ for idx, j in enumerate(supersets):
122
+ self._check_dict_like(j, check_values=False, name="arg #%d" % (idx + 1))
123
+ for k in j:
124
+ superdict.update({k: j[k]})
125
+
126
+ for i in self.val:
127
+ if i not in superdict:
128
+ missing.append({i: self.val[i]}) # bad key
129
+ elif self.val[i] != superdict[i]:
130
+ missing.append({i: self.val[i]}) # bad val
131
+ if missing:
132
+ return self.error(
133
+ "Expected <%s> to be subset of %s, but %s %s missing."
134
+ % (
135
+ self.val,
136
+ self._fmt_items(superdict),
137
+ self._fmt_items(missing),
138
+ "was" if len(missing) == 1 else "were",
139
+ )
140
+ )
141
+ else:
142
+ # flatten supersets
143
+ superset = set()
144
+ for j in supersets:
145
+ try:
146
+ for k in j:
147
+ superset.add(k)
148
+ except TypeError:
149
+ superset.add(j)
150
+
151
+ for i in self.val:
152
+ if i not in superset:
153
+ missing.append(i)
154
+ if missing:
155
+ return self.error(
156
+ "Expected <%s> to be subset of %s, but %s %s missing."
157
+ % (
158
+ self.val,
159
+ self._fmt_items(superset),
160
+ self._fmt_items(missing),
161
+ "was" if len(missing) == 1 else "were",
162
+ )
163
+ )
164
+
165
+ return self
166
+
167
+ def is_sorted(self, key=lambda x: x, reverse=False) -> Self:
168
+ """Asserts that val is iterable and is sorted.
169
+
170
+ Args:
171
+ key (function): the one-arg function to extract the sort comparison key. Defaults to
172
+ ``lambda x: x`` to just compare items directly.
173
+ reverse (bool): if ``True``, then comparison key is reversed. Defaults to ``False``.
174
+
175
+ Examples:
176
+ Usage::
177
+
178
+ assert_that(['a', 'b', 'c']).is_sorted()
179
+ assert_that((1, 2, 3)).is_sorted()
180
+
181
+ # with a key function
182
+ assert_that('aBc').is_sorted(key=str.lower)
183
+
184
+ # reverse order
185
+ assert_that(['c', 'b', 'a']).is_sorted(reverse=True)
186
+ assert_that((3, 2, 1)).is_sorted(reverse=True)
187
+
188
+ assert_that((1, 2, 3, 4, -5, 6)).is_sorted() # fails
189
+ # Expected <(1, 2, 3, 4, -5, 6)> to be sorted, but subset <4, -5> at index 3 is not.
190
+
191
+ Returns:
192
+ AssertionBuilder: returns this instance to chain to the next assertion
193
+
194
+ Raises:
195
+ AssertionError: if val is **not** sorted
196
+ """
197
+ if not isinstance(self.val, collections.abc.Iterable):
198
+ raise TypeError("val is not iterable")
199
+
200
+ prev = None
201
+ for i, x in enumerate(self.val):
202
+ if i > 0:
203
+ if reverse:
204
+ if key(x) > key(prev):
205
+ return self.error(
206
+ "Expected <%s> to be sorted reverse, but subset %s at index %s is not."
207
+ % (self.val, self._fmt_items([prev, x]), i - 1)
208
+ )
209
+ else:
210
+ if key(x) < key(prev):
211
+ return self.error(
212
+ "Expected <%s> to be sorted, but subset %s at index %s is not."
213
+ % (self.val, self._fmt_items([prev, x]), i - 1)
214
+ )
215
+ prev = x
216
+
217
+ return self