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/numeric.py
ADDED
|
@@ -0,0 +1,696 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import datetime
|
|
4
|
+
import numbers
|
|
5
|
+
from typing import TYPE_CHECKING, SupportsFloat, SupportsIndex
|
|
6
|
+
|
|
7
|
+
from ._engine._compare import _is_infinite, _is_nan, within_either
|
|
8
|
+
from ._engine._mixin_base import _MixinBase
|
|
9
|
+
from ._engine._ordering import UnorderableError, compare, holds, require_integer
|
|
10
|
+
from ._engine._require import _shown, argument, refuse, require_type
|
|
11
|
+
from ._hints import apart_in_time, out_of_order, under
|
|
12
|
+
from .errors import _capped, _capped_format
|
|
13
|
+
|
|
14
|
+
if TYPE_CHECKING:
|
|
15
|
+
from ._engine._compat import Self
|
|
16
|
+
|
|
17
|
+
__tracebackhide__ = True
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def _fmt_tolerance(tolerance: datetime.timedelta) -> str:
|
|
21
|
+
"""A timedelta tolerance as ``h:mm:ss``, its hours counted on past a day, with its fraction where it has one."""
|
|
22
|
+
hours, remainder = divmod(tolerance.days * 86400 + tolerance.seconds, 3600)
|
|
23
|
+
minutes, seconds = divmod(remainder, 60)
|
|
24
|
+
fraction = f".{tolerance.microseconds:06d}" if tolerance.microseconds else ""
|
|
25
|
+
return f"{hours}:{minutes:02d}:{seconds:02d}{fraction}"
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def _allowance(tolerance: object, rel: object) -> str:
|
|
29
|
+
"""The tolerances a closeness was asked within, in the words of its message."""
|
|
30
|
+
relative = f"relative tolerance <{_capped_format(rel)}>"
|
|
31
|
+
if tolerance is None:
|
|
32
|
+
return relative
|
|
33
|
+
absolute = f"tolerance <{_capped_format(tolerance)}>"
|
|
34
|
+
return absolute if rel is None else f"{absolute} or {relative}"
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
class NumericMixin(_MixinBase):
|
|
38
|
+
"""Numeric assertions mixin."""
|
|
39
|
+
|
|
40
|
+
_NUMERIC_COMPAREABLE = frozenset({datetime.datetime, datetime.timedelta, datetime.date, datetime.time})
|
|
41
|
+
_NUMERIC_NON_COMPAREABLE = frozenset({complex})
|
|
42
|
+
|
|
43
|
+
def _validate_compareable(self, other):
|
|
44
|
+
"""Refuse the pair when it cannot be ordered, in the wording this surface uses.
|
|
45
|
+
|
|
46
|
+
Whether it can be is decided in `_engine._ordering`, which the matchers ask as well: the rule
|
|
47
|
+
used to be written once here as a list of types and once there as a `try`/`except`, and one
|
|
48
|
+
relation spelled twice is how the text matchers drifted apart on bytes.
|
|
49
|
+
"""
|
|
50
|
+
try:
|
|
51
|
+
compare(self.val, other)
|
|
52
|
+
except UnorderableError as unordered:
|
|
53
|
+
self._refuse_unordered(unordered, other, "other")
|
|
54
|
+
|
|
55
|
+
def _refuse_unordered(self, unordered, other, name):
|
|
56
|
+
"""The refusal for a pair the ordering engine could not order, naming the argument it came from."""
|
|
57
|
+
if unordered.kind == "value":
|
|
58
|
+
refuse(self.val, "a value with an ordering (complex numbers have none)")
|
|
59
|
+
kind_bound = unordered.wanted
|
|
60
|
+
if unordered.kind == "kind":
|
|
61
|
+
wanted = (
|
|
62
|
+
"a number"
|
|
63
|
+
if kind_bound is numbers.Number or kind_bound is None
|
|
64
|
+
else f"a {kind_bound.__name__}, to match val"
|
|
65
|
+
)
|
|
66
|
+
refuse(other, wanted, subject=argument(name))
|
|
67
|
+
# the pair cannot be ordered, not the value: two strings compare fine, so that was untrue of `"10" > 5`
|
|
68
|
+
refuse(other, f"comparable with val {_shown(self.val)}", subject=argument(name))
|
|
69
|
+
|
|
70
|
+
def _within(self, low, high, *, named=None):
|
|
71
|
+
"""Whether ``low <= val <= high``, asked through the ordering engine so a NaN answers rather than signals.
|
|
72
|
+
|
|
73
|
+
A pair the engine cannot order is refused where it is met, in the words the single relations use,
|
|
74
|
+
and only there: below the low bound the answer is known, and the high one is never asked. Asked
|
|
75
|
+
up front instead, a high bound the value could not be ordered against turned a passing
|
|
76
|
+
`is_not_between` into a refusal.
|
|
77
|
+
|
|
78
|
+
*named* is what a refusal names, as ``(argument, its name)``: a window's bounds are worked out from
|
|
79
|
+
`other`, and `other` is what the caller wrote.
|
|
80
|
+
"""
|
|
81
|
+
return self._holds(low, "ge", named or (low, "low")) and self._holds(high, "le", named or (high, "high"))
|
|
82
|
+
|
|
83
|
+
def _close(self, other, tolerance, rel):
|
|
84
|
+
"""Whether val is within *tolerance* or *rel* of *other*, *rel* being a fraction of the size of *other*.
|
|
85
|
+
|
|
86
|
+
A pair the ordering engine cannot order is refused, in the words the single relations use.
|
|
87
|
+
"""
|
|
88
|
+
try:
|
|
89
|
+
return within_either(self.val, other, tolerance, rel)
|
|
90
|
+
except UnorderableError as unordered:
|
|
91
|
+
self._refuse_unordered(unordered, other, "other")
|
|
92
|
+
|
|
93
|
+
def _holds(self, bound, relation, named):
|
|
94
|
+
"""`holds` against one bound, with a pair the engine cannot order refused under *named*."""
|
|
95
|
+
try:
|
|
96
|
+
return holds(self.val, bound, relation)
|
|
97
|
+
except UnorderableError as unordered:
|
|
98
|
+
self._refuse_unordered(unordered, *named)
|
|
99
|
+
|
|
100
|
+
def _validate_number(self):
|
|
101
|
+
"""Raise TypeError if val is not numeric."""
|
|
102
|
+
require_type(self.val, numbers.Number, "a number")
|
|
103
|
+
|
|
104
|
+
def _validate_real(self):
|
|
105
|
+
"""Raise TypeError if val is not real number."""
|
|
106
|
+
require_type(self.val, numbers.Real, "a real number")
|
|
107
|
+
|
|
108
|
+
def is_zero(self) -> Self:
|
|
109
|
+
"""Asserts that val is numeric and is zero.
|
|
110
|
+
|
|
111
|
+
Examples:
|
|
112
|
+
Usage:
|
|
113
|
+
|
|
114
|
+
assert_that(0).is_zero()
|
|
115
|
+
|
|
116
|
+
Returns:
|
|
117
|
+
AssertionBuilder: returns this instance to chain to the next assertion
|
|
118
|
+
|
|
119
|
+
Raises:
|
|
120
|
+
AssertionError: if val is **not** zero
|
|
121
|
+
"""
|
|
122
|
+
self._validate_number()
|
|
123
|
+
return self.is_equal_to(0)
|
|
124
|
+
|
|
125
|
+
def is_not_zero(self) -> Self:
|
|
126
|
+
"""Asserts that val is numeric and is *not* zero.
|
|
127
|
+
|
|
128
|
+
Examples:
|
|
129
|
+
Usage:
|
|
130
|
+
|
|
131
|
+
assert_that(1).is_not_zero()
|
|
132
|
+
assert_that(123.4).is_not_zero()
|
|
133
|
+
|
|
134
|
+
Returns:
|
|
135
|
+
AssertionBuilder: returns this instance to chain to the next assertion
|
|
136
|
+
|
|
137
|
+
Raises:
|
|
138
|
+
AssertionError: if val **is** zero
|
|
139
|
+
"""
|
|
140
|
+
self._validate_number()
|
|
141
|
+
return self.is_not_equal_to(0)
|
|
142
|
+
|
|
143
|
+
def is_nan(self) -> Self:
|
|
144
|
+
"""Asserts that val is real number and is ``NaN`` (not a number).
|
|
145
|
+
|
|
146
|
+
Examples:
|
|
147
|
+
Usage:
|
|
148
|
+
|
|
149
|
+
assert_that(float('nan')).is_nan()
|
|
150
|
+
assert_that(float('inf') * 0).is_nan()
|
|
151
|
+
|
|
152
|
+
Returns:
|
|
153
|
+
AssertionBuilder: returns this instance to chain to the next assertion
|
|
154
|
+
|
|
155
|
+
Raises:
|
|
156
|
+
AssertionError: if val is **not** NaN
|
|
157
|
+
"""
|
|
158
|
+
self._validate_number()
|
|
159
|
+
self._validate_real()
|
|
160
|
+
if not _is_nan(self.val):
|
|
161
|
+
return self.error(f"Expected <{_capped(self.val)}> to be <NaN>, but was not.")
|
|
162
|
+
return self
|
|
163
|
+
|
|
164
|
+
def is_not_nan(self) -> Self:
|
|
165
|
+
"""Asserts that val is real number and is *not* ``NaN`` (not a number).
|
|
166
|
+
|
|
167
|
+
Examples:
|
|
168
|
+
Usage:
|
|
169
|
+
|
|
170
|
+
assert_that(0).is_not_nan()
|
|
171
|
+
assert_that(123.4).is_not_nan()
|
|
172
|
+
assert_that(float('inf')).is_not_nan()
|
|
173
|
+
|
|
174
|
+
Returns:
|
|
175
|
+
AssertionBuilder: returns this instance to chain to the next assertion
|
|
176
|
+
|
|
177
|
+
Raises:
|
|
178
|
+
AssertionError: if val **is** NaN
|
|
179
|
+
"""
|
|
180
|
+
self._validate_number()
|
|
181
|
+
self._validate_real()
|
|
182
|
+
if _is_nan(self.val):
|
|
183
|
+
return self.error("Expected not <NaN>, but was.")
|
|
184
|
+
return self
|
|
185
|
+
|
|
186
|
+
def is_inf(self) -> Self:
|
|
187
|
+
"""Asserts that val is real number and is ``Inf`` (infinity).
|
|
188
|
+
|
|
189
|
+
Examples:
|
|
190
|
+
Usage:
|
|
191
|
+
|
|
192
|
+
assert_that(float('inf')).is_inf()
|
|
193
|
+
assert_that(float('inf') * 1).is_inf()
|
|
194
|
+
|
|
195
|
+
Returns:
|
|
196
|
+
AssertionBuilder: returns this instance to chain to the next assertion
|
|
197
|
+
|
|
198
|
+
Raises:
|
|
199
|
+
AssertionError: if val is **not** Inf
|
|
200
|
+
"""
|
|
201
|
+
self._validate_number()
|
|
202
|
+
self._validate_real()
|
|
203
|
+
if not _is_infinite(self.val):
|
|
204
|
+
return self.error(f"Expected <{_capped(self.val)}> to be <Inf>, but was not.")
|
|
205
|
+
return self
|
|
206
|
+
|
|
207
|
+
def is_not_inf(self) -> Self:
|
|
208
|
+
"""Asserts that val is real number and is *not* ``Inf`` (infinity).
|
|
209
|
+
|
|
210
|
+
Examples:
|
|
211
|
+
Usage:
|
|
212
|
+
|
|
213
|
+
assert_that(0).is_not_inf()
|
|
214
|
+
assert_that(123.4).is_not_inf()
|
|
215
|
+
assert_that(float('nan')).is_not_inf()
|
|
216
|
+
|
|
217
|
+
Returns:
|
|
218
|
+
AssertionBuilder: returns this instance to chain to the next assertion
|
|
219
|
+
|
|
220
|
+
Raises:
|
|
221
|
+
AssertionError: if val **is** Inf
|
|
222
|
+
"""
|
|
223
|
+
self._validate_number()
|
|
224
|
+
self._validate_real()
|
|
225
|
+
if _is_infinite(self.val):
|
|
226
|
+
return self.error("Expected not <Inf>, but was.")
|
|
227
|
+
return self
|
|
228
|
+
|
|
229
|
+
def is_greater_than(self, other: object) -> Self:
|
|
230
|
+
"""Asserts that val is numeric and is greater than other.
|
|
231
|
+
|
|
232
|
+
Args:
|
|
233
|
+
other: the other date, expected to be less than val
|
|
234
|
+
|
|
235
|
+
Examples:
|
|
236
|
+
Usage:
|
|
237
|
+
|
|
238
|
+
assert_that(1).is_greater_than(0)
|
|
239
|
+
assert_that(123.4).is_greater_than(111.1)
|
|
240
|
+
|
|
241
|
+
For dates, behavior is identical to [`is_after()`][assertpy.date.DateMixin.is_after]:
|
|
242
|
+
|
|
243
|
+
import datetime
|
|
244
|
+
|
|
245
|
+
today = datetime.datetime.now()
|
|
246
|
+
yesterday = today - datetime.timedelta(days=1)
|
|
247
|
+
|
|
248
|
+
assert_that(today).is_greater_than(yesterday)
|
|
249
|
+
|
|
250
|
+
Returns:
|
|
251
|
+
AssertionBuilder: returns this instance to chain to the next assertion
|
|
252
|
+
|
|
253
|
+
Raises:
|
|
254
|
+
AssertionError: if val is **not** greater than other
|
|
255
|
+
|
|
256
|
+
Note:
|
|
257
|
+
A ``NaN`` value is unordered, so it **fails** every relational assertion here
|
|
258
|
+
(``is_greater_than``, ``is_less_than``, ``is_between``, ``is_positive``, ...). This diverges
|
|
259
|
+
from assertpy 1.x, where ``NaN`` silently passes.
|
|
260
|
+
"""
|
|
261
|
+
self._validate_compareable(other)
|
|
262
|
+
if not holds(self.val, other, "gt"): # positive form so NaN (unordered) fails instead of slipping through
|
|
263
|
+
return self.error(
|
|
264
|
+
f"Expected <{_capped_format(self.val)}> to be greater than <{_capped_format(other)}>, but was not."
|
|
265
|
+
f"{under(out_of_order(self.val, other, before=False, strict=True))}",
|
|
266
|
+
expected=other,
|
|
267
|
+
)
|
|
268
|
+
return self
|
|
269
|
+
|
|
270
|
+
def is_greater_than_or_equal_to(self, other: object) -> Self:
|
|
271
|
+
"""Asserts that val is numeric and is greater than or equal to other.
|
|
272
|
+
|
|
273
|
+
Args:
|
|
274
|
+
other: the other date, expected to be less than or equal to val
|
|
275
|
+
|
|
276
|
+
Examples:
|
|
277
|
+
Usage:
|
|
278
|
+
|
|
279
|
+
assert_that(1).is_greater_than_or_equal_to(0)
|
|
280
|
+
assert_that(1).is_greater_than_or_equal_to(1)
|
|
281
|
+
assert_that(123.4).is_greater_than_or_equal_to(111.1)
|
|
282
|
+
|
|
283
|
+
For dates, behavior is identical to [`is_after()`][assertpy.date.DateMixin.is_after] *except* when equal:
|
|
284
|
+
|
|
285
|
+
import datetime
|
|
286
|
+
|
|
287
|
+
today = datetime.datetime.now()
|
|
288
|
+
yesterday = today - datetime.timedelta(days=1)
|
|
289
|
+
|
|
290
|
+
assert_that(today).is_greater_than_or_equal_to(yesterday)
|
|
291
|
+
assert_that(today).is_greater_than_or_equal_to(today)
|
|
292
|
+
|
|
293
|
+
Returns:
|
|
294
|
+
AssertionBuilder: returns this instance to chain to the next assertion
|
|
295
|
+
|
|
296
|
+
Raises:
|
|
297
|
+
AssertionError: if val is **not** greater than or equal to other
|
|
298
|
+
"""
|
|
299
|
+
self._validate_compareable(other)
|
|
300
|
+
if not holds(self.val, other, "ge"): # positive form so NaN (unordered) fails instead of slipping through
|
|
301
|
+
return self.error(
|
|
302
|
+
f"Expected <{_capped_format(self.val)}> to be greater than or equal to"
|
|
303
|
+
f" <{_capped_format(other)}>, but was not."
|
|
304
|
+
f"{under(out_of_order(self.val, other, before=False, strict=False))}",
|
|
305
|
+
expected=other,
|
|
306
|
+
)
|
|
307
|
+
return self
|
|
308
|
+
|
|
309
|
+
def is_less_than(self, other: object) -> Self:
|
|
310
|
+
"""Asserts that val is numeric and is less than other.
|
|
311
|
+
|
|
312
|
+
Args:
|
|
313
|
+
other: the other date, expected to be greater than val
|
|
314
|
+
|
|
315
|
+
Examples:
|
|
316
|
+
Usage:
|
|
317
|
+
|
|
318
|
+
assert_that(0).is_less_than(1)
|
|
319
|
+
assert_that(123.4).is_less_than(555.5)
|
|
320
|
+
|
|
321
|
+
For dates, behavior is identical to [`is_before()`][assertpy.date.DateMixin.is_before]:
|
|
322
|
+
|
|
323
|
+
import datetime
|
|
324
|
+
|
|
325
|
+
today = datetime.datetime.now()
|
|
326
|
+
yesterday = today - datetime.timedelta(days=1)
|
|
327
|
+
|
|
328
|
+
assert_that(yesterday).is_less_than(today)
|
|
329
|
+
|
|
330
|
+
Returns:
|
|
331
|
+
AssertionBuilder: returns this instance to chain to the next assertion
|
|
332
|
+
|
|
333
|
+
Raises:
|
|
334
|
+
AssertionError: if val is **not** less than other
|
|
335
|
+
|
|
336
|
+
Note:
|
|
337
|
+
A ``NaN`` value is unordered and **fails** here, diverging from assertpy 1.x where
|
|
338
|
+
``NaN`` silently passes relational assertions.
|
|
339
|
+
"""
|
|
340
|
+
self._validate_compareable(other)
|
|
341
|
+
if not holds(self.val, other, "lt"): # positive form so NaN (unordered) fails instead of slipping through
|
|
342
|
+
return self.error(
|
|
343
|
+
f"Expected <{_capped_format(self.val)}> to be less than <{_capped_format(other)}>, but was not."
|
|
344
|
+
f"{under(out_of_order(self.val, other, before=True, strict=True))}",
|
|
345
|
+
expected=other,
|
|
346
|
+
)
|
|
347
|
+
return self
|
|
348
|
+
|
|
349
|
+
def is_less_than_or_equal_to(self, other: object) -> Self:
|
|
350
|
+
"""Asserts that val is numeric and is less than or equal to other.
|
|
351
|
+
|
|
352
|
+
Args:
|
|
353
|
+
other: the other date, expected to be greater than or equal to val
|
|
354
|
+
|
|
355
|
+
Examples:
|
|
356
|
+
Usage:
|
|
357
|
+
|
|
358
|
+
assert_that(1).is_less_than_or_equal_to(0)
|
|
359
|
+
assert_that(1).is_less_than_or_equal_to(1)
|
|
360
|
+
assert_that(123.4).is_less_than_or_equal_to(100.0)
|
|
361
|
+
|
|
362
|
+
For dates, behavior is identical to [`is_before()`][assertpy.date.DateMixin.is_before]
|
|
363
|
+
*except* when equal:
|
|
364
|
+
|
|
365
|
+
import datetime
|
|
366
|
+
|
|
367
|
+
today = datetime.datetime.now()
|
|
368
|
+
yesterday = today - datetime.timedelta(days=1)
|
|
369
|
+
|
|
370
|
+
assert_that(yesterday).is_less_than_or_equal_to(today)
|
|
371
|
+
assert_that(today).is_less_than_or_equal_to(today)
|
|
372
|
+
|
|
373
|
+
Returns:
|
|
374
|
+
AssertionBuilder: returns this instance to chain to the next assertion
|
|
375
|
+
|
|
376
|
+
Raises:
|
|
377
|
+
AssertionError: if val is **not** less than or equal to other
|
|
378
|
+
"""
|
|
379
|
+
self._validate_compareable(other)
|
|
380
|
+
if not holds(self.val, other, "le"): # positive form so NaN (unordered) fails instead of slipping through
|
|
381
|
+
return self.error(
|
|
382
|
+
f"Expected <{_capped_format(self.val)}> to be less than or equal to"
|
|
383
|
+
f" <{_capped_format(other)}>, but was not."
|
|
384
|
+
f"{under(out_of_order(self.val, other, before=True, strict=False))}",
|
|
385
|
+
expected=other,
|
|
386
|
+
)
|
|
387
|
+
return self
|
|
388
|
+
|
|
389
|
+
def is_positive(self) -> Self:
|
|
390
|
+
"""Asserts that val is numeric and is greater than zero.
|
|
391
|
+
|
|
392
|
+
Examples:
|
|
393
|
+
Usage:
|
|
394
|
+
|
|
395
|
+
assert_that(1).is_positive()
|
|
396
|
+
assert_that(123.4).is_positive()
|
|
397
|
+
|
|
398
|
+
Returns:
|
|
399
|
+
AssertionBuilder: returns this instance to chain to the next assertion
|
|
400
|
+
|
|
401
|
+
Raises:
|
|
402
|
+
AssertionError: if val is **not** positive
|
|
403
|
+
|
|
404
|
+
Note:
|
|
405
|
+
``NaN`` is not positive, so this **fails** for ``float("nan")``. This diverges from the
|
|
406
|
+
assertpy 1.x, where ``NaN`` silently passes relational assertions.
|
|
407
|
+
"""
|
|
408
|
+
return self.is_greater_than(0)
|
|
409
|
+
|
|
410
|
+
def is_negative(self) -> Self:
|
|
411
|
+
"""Asserts that val is numeric and is less than zero.
|
|
412
|
+
|
|
413
|
+
Examples:
|
|
414
|
+
Usage:
|
|
415
|
+
|
|
416
|
+
assert_that(-1).is_negative()
|
|
417
|
+
assert_that(-123.4).is_negative()
|
|
418
|
+
|
|
419
|
+
Returns:
|
|
420
|
+
AssertionBuilder: returns this instance to chain to the next assertion
|
|
421
|
+
|
|
422
|
+
Raises:
|
|
423
|
+
AssertionError: if val is **not** negative
|
|
424
|
+
|
|
425
|
+
Note:
|
|
426
|
+
``NaN`` is not negative, so this **fails** for ``float("nan")``. This diverges from the
|
|
427
|
+
assertpy 1.x, where ``NaN`` silently passes relational assertions.
|
|
428
|
+
"""
|
|
429
|
+
return self.is_less_than(0)
|
|
430
|
+
|
|
431
|
+
def is_between(self, low: object, high: object) -> Self:
|
|
432
|
+
"""Asserts that val is numeric and is between low and high.
|
|
433
|
+
|
|
434
|
+
Args:
|
|
435
|
+
low: the low value
|
|
436
|
+
high: the high value
|
|
437
|
+
|
|
438
|
+
Examples:
|
|
439
|
+
Usage:
|
|
440
|
+
|
|
441
|
+
assert_that(1).is_between(0, 2)
|
|
442
|
+
assert_that(123.4).is_between(111.1, 222.2)
|
|
443
|
+
|
|
444
|
+
For dates, works as expected:
|
|
445
|
+
|
|
446
|
+
import datetime
|
|
447
|
+
|
|
448
|
+
today = datetime.datetime.now()
|
|
449
|
+
middle = today - datetime.timedelta(hours=12)
|
|
450
|
+
yesterday = today - datetime.timedelta(days=1)
|
|
451
|
+
|
|
452
|
+
assert_that(middle).is_between(yesterday, today)
|
|
453
|
+
|
|
454
|
+
Returns:
|
|
455
|
+
AssertionBuilder: returns this instance to chain to the next assertion
|
|
456
|
+
|
|
457
|
+
Raises:
|
|
458
|
+
AssertionError: if val is **not** between low and high
|
|
459
|
+
|
|
460
|
+
Note:
|
|
461
|
+
``NaN`` is not within any range, so this **fails** for ``float("nan")``. This diverges from
|
|
462
|
+
assertpy 1.x, where ``NaN`` silently passes relational assertions.
|
|
463
|
+
"""
|
|
464
|
+
val_type = type(self.val)
|
|
465
|
+
self._validate_between_args(val_type, low, high)
|
|
466
|
+
|
|
467
|
+
if not self._within(low, high): # positive form so NaN (unordered) fails instead of passing
|
|
468
|
+
return self.error(
|
|
469
|
+
f"Expected <{_capped_format(self.val)}> to be between"
|
|
470
|
+
f" <{_capped_format(low)}> and <{_capped_format(high)}>, but was not.",
|
|
471
|
+
expected=(low, high),
|
|
472
|
+
)
|
|
473
|
+
return self
|
|
474
|
+
|
|
475
|
+
def is_not_between(self, low: object, high: object) -> Self:
|
|
476
|
+
"""Asserts that val is numeric and is *not* between low and high.
|
|
477
|
+
|
|
478
|
+
Args:
|
|
479
|
+
low: the low value
|
|
480
|
+
high: the high value
|
|
481
|
+
|
|
482
|
+
Examples:
|
|
483
|
+
Usage:
|
|
484
|
+
|
|
485
|
+
assert_that(1).is_not_between(2, 3)
|
|
486
|
+
assert_that(1.1).is_not_between(2.2, 3.3)
|
|
487
|
+
|
|
488
|
+
Returns:
|
|
489
|
+
AssertionBuilder: returns this instance to chain to the next assertion
|
|
490
|
+
|
|
491
|
+
Raises:
|
|
492
|
+
AssertionError: if val **is** between low and high
|
|
493
|
+
"""
|
|
494
|
+
val_type = type(self.val)
|
|
495
|
+
self._validate_between_args(val_type, low, high)
|
|
496
|
+
|
|
497
|
+
if self._within(low, high):
|
|
498
|
+
return self.error(
|
|
499
|
+
f"Expected <{_capped_format(self.val)}> to not be between"
|
|
500
|
+
f" <{_capped_format(low)}> and <{_capped_format(high)}>, but was."
|
|
501
|
+
)
|
|
502
|
+
return self
|
|
503
|
+
|
|
504
|
+
def _integer_value(self) -> int:
|
|
505
|
+
value = self.val
|
|
506
|
+
return value if type(value) is int else require_integer(value)
|
|
507
|
+
|
|
508
|
+
def is_even(self) -> Self:
|
|
509
|
+
"""Asserts that val is an integer and is even.
|
|
510
|
+
|
|
511
|
+
Examples:
|
|
512
|
+
Usage:
|
|
513
|
+
|
|
514
|
+
assert_that(0).is_even()
|
|
515
|
+
assert_that(2).is_even()
|
|
516
|
+
assert_that(-4).is_even()
|
|
517
|
+
|
|
518
|
+
Returns:
|
|
519
|
+
AssertionBuilder: returns this instance to chain to the next assertion
|
|
520
|
+
|
|
521
|
+
Raises:
|
|
522
|
+
AssertionError: if val is **not** even
|
|
523
|
+
"""
|
|
524
|
+
if self._integer_value() % 2 != 0:
|
|
525
|
+
return self.error(f"Expected <{_capped(self.val)}> to be even, but was not.")
|
|
526
|
+
return self
|
|
527
|
+
|
|
528
|
+
def is_odd(self) -> Self:
|
|
529
|
+
"""Asserts that val is an integer and is odd.
|
|
530
|
+
|
|
531
|
+
Examples:
|
|
532
|
+
Usage:
|
|
533
|
+
|
|
534
|
+
assert_that(1).is_odd()
|
|
535
|
+
assert_that(3).is_odd()
|
|
536
|
+
assert_that(-5).is_odd()
|
|
537
|
+
|
|
538
|
+
Returns:
|
|
539
|
+
AssertionBuilder: returns this instance to chain to the next assertion
|
|
540
|
+
|
|
541
|
+
Raises:
|
|
542
|
+
AssertionError: if val is **not** odd
|
|
543
|
+
"""
|
|
544
|
+
if self._integer_value() % 2 == 0:
|
|
545
|
+
return self.error(f"Expected <{_capped(self.val)}> to be odd, but was not.")
|
|
546
|
+
return self
|
|
547
|
+
|
|
548
|
+
def is_divisible_by(self, divisor: SupportsIndex) -> Self:
|
|
549
|
+
"""Asserts that val is an integer and is divisible by divisor.
|
|
550
|
+
|
|
551
|
+
Args:
|
|
552
|
+
divisor: the divisor to check against (must be a non-zero integer)
|
|
553
|
+
|
|
554
|
+
Examples:
|
|
555
|
+
Usage:
|
|
556
|
+
|
|
557
|
+
assert_that(10).is_divisible_by(5)
|
|
558
|
+
assert_that(12).is_divisible_by(3)
|
|
559
|
+
|
|
560
|
+
Returns:
|
|
561
|
+
AssertionBuilder: returns this instance to chain to the next assertion
|
|
562
|
+
|
|
563
|
+
Raises:
|
|
564
|
+
AssertionError: if val is **not** divisible by divisor
|
|
565
|
+
"""
|
|
566
|
+
value = self._integer_value()
|
|
567
|
+
modulus = divisor if type(divisor) is int else require_integer(divisor, "divisor")
|
|
568
|
+
if modulus == 0:
|
|
569
|
+
raise ValueError("given divisor arg must not be zero")
|
|
570
|
+
if value % modulus != 0:
|
|
571
|
+
return self.error(
|
|
572
|
+
f"Expected <{_capped(self.val)}> to be divisible by <{_capped_format(divisor)}>, but was not.",
|
|
573
|
+
expected=divisor,
|
|
574
|
+
)
|
|
575
|
+
return self
|
|
576
|
+
|
|
577
|
+
def is_close_to(
|
|
578
|
+
self,
|
|
579
|
+
other: SupportsFloat | SupportsIndex | datetime.datetime,
|
|
580
|
+
tolerance: SupportsFloat | SupportsIndex | datetime.timedelta | None = None,
|
|
581
|
+
*,
|
|
582
|
+
rel: SupportsFloat | SupportsIndex | None = None,
|
|
583
|
+
) -> Self:
|
|
584
|
+
"""Asserts that val is numeric and is close to other within tolerance.
|
|
585
|
+
|
|
586
|
+
The distance is taken three ways, the difference and the window around each side, and any of them
|
|
587
|
+
holding is enough, so under ``tolerance`` the answer is the same whichever operand is val. An
|
|
588
|
+
infinity is close only to itself, whatever the tolerance, and a NaN to nothing.
|
|
589
|
+
|
|
590
|
+
``rel`` is a tolerance relative to the size of other, the expected value, as `pytest.approx` scales
|
|
591
|
+
it, and so it reads one way round: ``rel=0.02`` allows ``2`` either side of ``100``, which ``98`` is
|
|
592
|
+
within, while ``100`` is not within ``rel=0.02`` of ``98``. It is then measured as ``tolerance`` is.
|
|
593
|
+
Given beside ``tolerance``, either holding is enough, which is what keeps an expected value near
|
|
594
|
+
zero, where any fraction of its size is tiny, within reach.
|
|
595
|
+
|
|
596
|
+
Args:
|
|
597
|
+
other (object): the other value, expected to be close to val within tolerance
|
|
598
|
+
tolerance (object): the absolute tolerance, zero or more
|
|
599
|
+
rel (object): the tolerance as a fraction of the size of other, zero or more; for numbers
|
|
600
|
+
|
|
601
|
+
Examples:
|
|
602
|
+
Usage:
|
|
603
|
+
|
|
604
|
+
assert_that(123).is_close_to(100, 25)
|
|
605
|
+
assert_that(123.4).is_close_to(123, 0.5)
|
|
606
|
+
assert_that(101.5).is_close_to(100, rel=0.02)
|
|
607
|
+
|
|
608
|
+
For dates, works as expected:
|
|
609
|
+
|
|
610
|
+
import datetime
|
|
611
|
+
|
|
612
|
+
today = datetime.datetime.now()
|
|
613
|
+
yesterday = today - datetime.timedelta(days=1)
|
|
614
|
+
|
|
615
|
+
assert_that(today).is_close_to(yesterday, datetime.timedelta(hours=36))
|
|
616
|
+
|
|
617
|
+
Returns:
|
|
618
|
+
AssertionBuilder: returns this instance to chain to the next assertion
|
|
619
|
+
|
|
620
|
+
Raises:
|
|
621
|
+
AssertionError: if val is **not** close to other within tolerance
|
|
622
|
+
TypeError: if val, other or tolerance is not a number (or a ``datetime`` and a ``timedelta``),
|
|
623
|
+
or is a bool, or if a numpy duration meets a number, or if neither tolerance nor rel is given,
|
|
624
|
+
or rel is given for a datetime
|
|
625
|
+
ValueError: if tolerance or rel is negative or NaN
|
|
626
|
+
"""
|
|
627
|
+
self._validate_close_to_args(self.val, other, tolerance, rel)
|
|
628
|
+
|
|
629
|
+
# read through `math.isnan`, which converts the value: the gate the capable facade keys this on
|
|
630
|
+
nan = not isinstance(self.val, datetime.datetime) and (_is_nan(self.val) or _is_nan(other))
|
|
631
|
+
if nan or not self._close(other, tolerance, rel):
|
|
632
|
+
if isinstance(tolerance, datetime.timedelta):
|
|
633
|
+
return self.error(
|
|
634
|
+
f"Expected <{_capped_format(self.val)}> to be close to"
|
|
635
|
+
f" <{_capped_format(other)}> within tolerance"
|
|
636
|
+
f" <{_fmt_tolerance(tolerance)}>, but was not."
|
|
637
|
+
f"{under(apart_in_time(self.val, other, tolerance, close=True))}",
|
|
638
|
+
expected=(other, tolerance),
|
|
639
|
+
)
|
|
640
|
+
else:
|
|
641
|
+
return self.error(
|
|
642
|
+
f"Expected <{_capped(self.val)}> to be close to <{_capped_format(other)}> within"
|
|
643
|
+
f" {_allowance(tolerance, rel)}, but was not.",
|
|
644
|
+
expected=(other, tolerance) if rel is None else (other, tolerance, rel),
|
|
645
|
+
)
|
|
646
|
+
return self
|
|
647
|
+
|
|
648
|
+
def is_not_close_to(
|
|
649
|
+
self,
|
|
650
|
+
other: SupportsFloat | SupportsIndex | datetime.datetime,
|
|
651
|
+
tolerance: SupportsFloat | SupportsIndex | datetime.timedelta | None = None,
|
|
652
|
+
*,
|
|
653
|
+
rel: SupportsFloat | SupportsIndex | None = None,
|
|
654
|
+
) -> Self:
|
|
655
|
+
"""Asserts that val is numeric and is *not* close to other within tolerance.
|
|
656
|
+
|
|
657
|
+
Measured as [`is_close_to()`][assertpy.numeric.NumericMixin.is_close_to] measures, so an infinity
|
|
658
|
+
is not close to anything but itself and a NaN to nothing.
|
|
659
|
+
|
|
660
|
+
Args:
|
|
661
|
+
other (object): the other value
|
|
662
|
+
tolerance (object): the absolute tolerance, zero or more
|
|
663
|
+
rel (object): the tolerance as a fraction of the size of other, zero or more; for numbers
|
|
664
|
+
|
|
665
|
+
Examples:
|
|
666
|
+
Usage:
|
|
667
|
+
|
|
668
|
+
assert_that(123).is_not_close_to(100, 22)
|
|
669
|
+
assert_that(123.4).is_not_close_to(123, 0.1)
|
|
670
|
+
|
|
671
|
+
Returns:
|
|
672
|
+
AssertionBuilder: returns this instance to chain to the next assertion
|
|
673
|
+
|
|
674
|
+
Raises:
|
|
675
|
+
AssertionError: if val **is** close to other within tolerance
|
|
676
|
+
TypeError: if val, other or tolerance is not a number (or a ``datetime`` and a ``timedelta``),
|
|
677
|
+
or is a bool, or if a numpy duration meets a number, or if neither tolerance nor rel is given,
|
|
678
|
+
or rel is given for a datetime
|
|
679
|
+
ValueError: if tolerance or rel is negative or NaN
|
|
680
|
+
"""
|
|
681
|
+
self._validate_close_to_args(self.val, other, tolerance, rel)
|
|
682
|
+
|
|
683
|
+
if self._close(other, tolerance, rel):
|
|
684
|
+
if isinstance(tolerance, datetime.timedelta):
|
|
685
|
+
return self.error(
|
|
686
|
+
f"Expected <{_capped_format(self.val)}> to not be close to"
|
|
687
|
+
f" <{_capped_format(other)}> within tolerance"
|
|
688
|
+
f" <{_fmt_tolerance(tolerance)}>, but was."
|
|
689
|
+
f"{under(apart_in_time(self.val, other, tolerance, close=False))}",
|
|
690
|
+
)
|
|
691
|
+
else:
|
|
692
|
+
return self.error(
|
|
693
|
+
f"Expected <{_capped(self.val)}> to not be close to <{_capped_format(other)}> within"
|
|
694
|
+
f" {_allowance(tolerance, rel)}, but was.",
|
|
695
|
+
)
|
|
696
|
+
return self
|