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/dataframe.py
ADDED
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
"""Optional fluent assertions for data-science containers (pandas / polars / numpy).
|
|
2
|
+
|
|
3
|
+
This is an *integration* layer in the same spirit as the Allure and Behave adapters: each library is its
|
|
4
|
+
own optional extra (``pip install assertpy[pandas]`` / ``[polars]`` / ``[numpy]``, or ``[data]`` for all
|
|
5
|
+
three), imported lazily by name, so the core stays free of runtime dependencies. Comparison
|
|
6
|
+
**semantics** are delegated to each library's own testing utilities (``assert_frame_equal`` /
|
|
7
|
+
``assert_series_equal`` / ``assert_array_equal`` / ``assert_allclose``), so dtype, tolerance and NaN
|
|
8
|
+
handling are the library's, with the exceptions this layer adds: ``is_array_equal`` compares the shapes
|
|
9
|
+
before numpy does, and ``is_array_close_to`` defaults its tolerances to those of ``numpy.isclose`` and
|
|
10
|
+
takes no ``NaN`` as equal unless asked. Otherwise this layer adds the fluent entry point and routes
|
|
11
|
+
failures through the standard assertpy error model.
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
from __future__ import annotations
|
|
15
|
+
|
|
16
|
+
import importlib
|
|
17
|
+
from typing import TYPE_CHECKING, Any
|
|
18
|
+
|
|
19
|
+
from ._engine._mixin_base import _MixinBase
|
|
20
|
+
from ._engine._require import refuse
|
|
21
|
+
|
|
22
|
+
if TYPE_CHECKING:
|
|
23
|
+
from ._engine._compat import Self
|
|
24
|
+
|
|
25
|
+
__tracebackhide__ = True
|
|
26
|
+
|
|
27
|
+
_FRAME_ROOTS = ("pandas", "polars")
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def _ensure_module(name: str) -> Any:
|
|
31
|
+
"""Import optional library *name* by string, or raise a clear ImportError pointing at its extra."""
|
|
32
|
+
try:
|
|
33
|
+
return importlib.import_module(name)
|
|
34
|
+
except ImportError:
|
|
35
|
+
raise ImportError(
|
|
36
|
+
f"{name} is required for these assertions. Install it with: pip install assertpy[{name}]"
|
|
37
|
+
) from None
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def _load(root: str) -> tuple[Any, Any]:
|
|
41
|
+
"""Return ``(library, library.testing)`` for *root* (``pandas``/``polars``/``numpy``)."""
|
|
42
|
+
library = _ensure_module(root)
|
|
43
|
+
return library, importlib.import_module(f"{root}.testing")
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
class DataFrameMixin(_MixinBase):
|
|
47
|
+
"""Fluent assertions for pandas/polars frames and numpy arrays (optional ``[data]`` extra).
|
|
48
|
+
|
|
49
|
+
Each hands the comparison to the owning library's testing utilities and reports a failure through the
|
|
50
|
+
standard error model, so soft assertions, ``check()``, ``described_as()`` and warn mode all apply.
|
|
51
|
+
"""
|
|
52
|
+
|
|
53
|
+
def is_frame_equal(self, expected: object, **options: Any) -> Self:
|
|
54
|
+
"""Asserts that a pandas/polars ``DataFrame`` or ``Series`` equals *expected*.
|
|
55
|
+
|
|
56
|
+
Delegates to the owning library's own ``assert_frame_equal`` / ``assert_series_equal``, so all
|
|
57
|
+
comparison semantics (dtype strictness, row/column order, tolerance, categoricals, ...) are the
|
|
58
|
+
library's. Any keyword options are passed straight through. *expected* has to be of the same
|
|
59
|
+
library and kind: a polars frame against a pandas one, or a ``DataFrame`` against a ``Series``, fails
|
|
60
|
+
with the library's own message about the type.
|
|
61
|
+
|
|
62
|
+
Args:
|
|
63
|
+
expected: the expected frame/series (same library and kind as val)
|
|
64
|
+
**options: keyword options forwarded to the library's ``assert_frame_equal`` /
|
|
65
|
+
``assert_series_equal`` (e.g. ``check_dtype=False``, ``check_exact=False``, ``rtol=1e-3``)
|
|
66
|
+
|
|
67
|
+
Examples:
|
|
68
|
+
Usage:
|
|
69
|
+
|
|
70
|
+
import pandas as pd
|
|
71
|
+
|
|
72
|
+
assert_that(pd.DataFrame({"a": [1, 2]})).is_frame_equal(pd.DataFrame({"a": [1, 2]}))
|
|
73
|
+
assert_that(actual).is_frame_equal(expected, check_dtype=False)
|
|
74
|
+
|
|
75
|
+
Returns:
|
|
76
|
+
AssertionBuilder: returns this instance to chain to the next assertion
|
|
77
|
+
|
|
78
|
+
Raises:
|
|
79
|
+
AssertionError: if the frames/series are not equal (carrying the library's own diff message)
|
|
80
|
+
TypeError: if val is not a pandas or polars ``DataFrame``/``Series`` (an ``Index`` is refused too)
|
|
81
|
+
ImportError: if the owning library is not installed
|
|
82
|
+
"""
|
|
83
|
+
actual = self.val
|
|
84
|
+
# walk the MRO so a user subclass resolves to the owning library through its base
|
|
85
|
+
root = next(
|
|
86
|
+
(
|
|
87
|
+
base.__module__.split(".", 1)[0]
|
|
88
|
+
for base in type(actual).__mro__
|
|
89
|
+
if base.__module__.split(".", 1)[0] in _FRAME_ROOTS
|
|
90
|
+
),
|
|
91
|
+
type(actual).__module__.split(".", 1)[0],
|
|
92
|
+
)
|
|
93
|
+
if root not in _FRAME_ROOTS:
|
|
94
|
+
refuse(actual, "a pandas or polars DataFrame/Series")
|
|
95
|
+
library, testing = _load(root)
|
|
96
|
+
class_name = type(actual).__name__
|
|
97
|
+
# isinstance handles real subclasses, the name check handles duck-typed frames a test may inject
|
|
98
|
+
if isinstance(actual, library.Series) or class_name == "Series":
|
|
99
|
+
assert_equal, label = testing.assert_series_equal, "Series"
|
|
100
|
+
elif isinstance(actual, library.DataFrame) or class_name == "DataFrame":
|
|
101
|
+
assert_equal, label = testing.assert_frame_equal, "DataFrame"
|
|
102
|
+
else:
|
|
103
|
+
# a pandas/polars object that is neither a DataFrame nor a Series (Index, Categorical, ...)
|
|
104
|
+
refuse(actual, "a pandas or polars DataFrame/Series")
|
|
105
|
+
try:
|
|
106
|
+
assert_equal(actual, expected, **options)
|
|
107
|
+
except AssertionError as exc:
|
|
108
|
+
return self.error(
|
|
109
|
+
f"Expected the {label} to equal the expected one, but they differ:\n{exc}",
|
|
110
|
+
suppress_context=True,
|
|
111
|
+
expected=expected,
|
|
112
|
+
)
|
|
113
|
+
return self
|
|
114
|
+
|
|
115
|
+
def is_array_equal(self, expected: object, **options: Any) -> Self:
|
|
116
|
+
"""Asserts that val equals *expected* element-wise, via numpy's ``assert_array_equal``.
|
|
117
|
+
|
|
118
|
+
Works on any array-likes numpy can coerce (``ndarray``, nested lists, ...). The shapes are compared
|
|
119
|
+
before numpy is asked, so a scalar is never broadcast over an array. Then every element must match
|
|
120
|
+
exactly, a ``NaN`` equal to a ``NaN`` in the same position, as numpy has it.
|
|
121
|
+
|
|
122
|
+
Args:
|
|
123
|
+
expected: the expected array-like
|
|
124
|
+
**options: keyword options forwarded to numpy's ``assert_array_equal``
|
|
125
|
+
(e.g. ``strict=True``, which compares the dtypes too, or ``err_msg="..."``)
|
|
126
|
+
|
|
127
|
+
Examples:
|
|
128
|
+
Usage:
|
|
129
|
+
|
|
130
|
+
import numpy as np
|
|
131
|
+
|
|
132
|
+
assert_that(np.array([1, 2, 3])).is_array_equal(np.array([1, 2, 3]))
|
|
133
|
+
assert_that(np.array([1, 2, 3])).is_array_equal(np.array([1, 2, 3]), strict=True)
|
|
134
|
+
assert_that(np.array([1, 2, 3])).is_array_equal([1, 2, 3])
|
|
135
|
+
|
|
136
|
+
Returns:
|
|
137
|
+
AssertionBuilder: returns this instance to chain to the next assertion
|
|
138
|
+
|
|
139
|
+
Raises:
|
|
140
|
+
AssertionError: if the shapes differ, or the arrays are not equal (carrying numpy's own diff message)
|
|
141
|
+
ImportError: if numpy is not installed
|
|
142
|
+
"""
|
|
143
|
+
numpy, testing = _load("numpy")
|
|
144
|
+
# shape first: left to numpy a scalar broadcasts, so an empty array "equals" 5, against the shape
|
|
145
|
+
# the docstring promises. numpy's own `strict` is about dtype and is passed through untouched
|
|
146
|
+
actual_shape, expected_shape = numpy.shape(self.val), numpy.shape(expected)
|
|
147
|
+
if actual_shape != expected_shape:
|
|
148
|
+
return self.error(
|
|
149
|
+
f"Expected an array of shape <{expected_shape}>, but was of shape <{actual_shape}>.",
|
|
150
|
+
expected=expected,
|
|
151
|
+
)
|
|
152
|
+
try:
|
|
153
|
+
testing.assert_array_equal(self.val, expected, **options)
|
|
154
|
+
except AssertionError as exc:
|
|
155
|
+
return self.error(
|
|
156
|
+
f"Expected the arrays to be equal, but they differ:\n{exc}", suppress_context=True, expected=expected
|
|
157
|
+
)
|
|
158
|
+
return self
|
|
159
|
+
|
|
160
|
+
def is_array_close_to(
|
|
161
|
+
self, expected: object, *, rtol: float = 1e-05, atol: float = 1e-08, equal_nan: bool = False, **options: Any
|
|
162
|
+
) -> Self:
|
|
163
|
+
"""Asserts that val is element-wise close to *expected*, via numpy's ``assert_allclose``.
|
|
164
|
+
|
|
165
|
+
The float-tolerant counterpart to [`is_array_equal()`][assertpy.dataframe.DataFrameMixin.is_array_equal],
|
|
166
|
+
for comparing computed arrays. The shapes are numpy's to compare, so a scalar broadcasts over an
|
|
167
|
+
array unless ``strict=True`` is passed, which numpy 2 accepts. ``equal_nan`` defaults to ``False``,
|
|
168
|
+
where numpy's own default is ``True``, so a ``NaN`` fails unless asked for.
|
|
169
|
+
|
|
170
|
+
Args:
|
|
171
|
+
expected: the expected array-like
|
|
172
|
+
rtol: relative tolerance (``1e-05``, as ``numpy.isclose`` has it, where ``assert_allclose`` has ``1e-07``)
|
|
173
|
+
atol: absolute tolerance (``1e-08``, as ``numpy.isclose`` has it, where ``assert_allclose`` has ``0``)
|
|
174
|
+
equal_nan: whether ``NaN`` in the same position compares equal (``False`` here, ``True`` in numpy)
|
|
175
|
+
**options: further keyword options forwarded to numpy's ``assert_allclose``
|
|
176
|
+
(e.g. ``err_msg="..."``, ``strict=True``)
|
|
177
|
+
|
|
178
|
+
Examples:
|
|
179
|
+
Usage:
|
|
180
|
+
|
|
181
|
+
import numpy as np
|
|
182
|
+
|
|
183
|
+
assert_that(np.array([1.0, 2.0])).is_array_close_to(np.array([1.0, 2.0000001]))
|
|
184
|
+
assert_that(np.array([np.nan])).is_array_close_to(np.array([np.nan]), equal_nan=True)
|
|
185
|
+
|
|
186
|
+
Returns:
|
|
187
|
+
AssertionBuilder: returns this instance to chain to the next assertion
|
|
188
|
+
|
|
189
|
+
Raises:
|
|
190
|
+
AssertionError: if the arrays are not close (carrying numpy's own diff message)
|
|
191
|
+
ImportError: if numpy is not installed
|
|
192
|
+
"""
|
|
193
|
+
_, testing = _load("numpy")
|
|
194
|
+
try:
|
|
195
|
+
testing.assert_allclose(self.val, expected, rtol=rtol, atol=atol, equal_nan=equal_nan, **options)
|
|
196
|
+
except AssertionError as exc:
|
|
197
|
+
return self.error(
|
|
198
|
+
f"Expected the arrays to be close, but they differ:\n{exc}", suppress_context=True, expected=expected
|
|
199
|
+
)
|
|
200
|
+
return self
|
assertpy/date.py
ADDED
|
@@ -0,0 +1,327 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import datetime
|
|
4
|
+
from typing import TYPE_CHECKING, cast
|
|
5
|
+
|
|
6
|
+
from ._engine._mixin_base import _MixinBase
|
|
7
|
+
from ._engine._require import argument, require_type
|
|
8
|
+
from ._hints import out_of_order, under
|
|
9
|
+
from .errors import _capped, _capped_format
|
|
10
|
+
|
|
11
|
+
if TYPE_CHECKING:
|
|
12
|
+
from ._engine._compat import Self
|
|
13
|
+
|
|
14
|
+
__tracebackhide__ = True
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def _require_datetime(value: object, label: str) -> None:
|
|
18
|
+
"""Raise ``TypeError`` unless *value* is exactly a `datetime.datetime`."""
|
|
19
|
+
require_type(value, datetime.datetime, "a datetime", subject=label)
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def _read_in_the_zone_of(value: datetime.datetime, reference: datetime.datetime) -> datetime.datetime:
|
|
23
|
+
"""*value* as the same instant reads on *reference*'s clock, so wall-clock fields compare like with like.
|
|
24
|
+
|
|
25
|
+
The guide says making both sides aware is what makes these comparisons well defined. Compared field
|
|
26
|
+
by field they were not: two instants five hours apart in different zones read equal to the second.
|
|
27
|
+
Both sides are aware or both naive by then, and a naive pair has no zone to be read in. One wall time
|
|
28
|
+
of a zone's repeated hour is two moments an hour apart, so the caller asks whether the two are one reading
|
|
29
|
+
too (`_one_reading`) where the value was read on another clock. Two on one zone object are left to their
|
|
30
|
+
wall clocks, as `==` is.
|
|
31
|
+
"""
|
|
32
|
+
return value.astimezone(reference.tzinfo) if reference.tzinfo is not None else value
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def _one_reading(value: datetime.datetime, instant: datetime.datetime, other: datetime.datetime) -> bool:
|
|
36
|
+
"""Whether two that read alike on one clock are the same reading of it, where a clock reads a time twice.
|
|
37
|
+
|
|
38
|
+
The offsets tell. Equal ones are one reading. Two readings of one time have the later moment at the
|
|
39
|
+
smaller offset, since the clock went back between them, and the later moment at the larger one is a clock
|
|
40
|
+
that went forward inside the unit, which reads it once. Asked of the `fold` instead, a clock that goes
|
|
41
|
+
back by less than the unit read two visits to one minute as one.
|
|
42
|
+
"""
|
|
43
|
+
# both are aware here: a naive pair has no zone to be read in and is never asked
|
|
44
|
+
held, read = cast("datetime.timedelta", value.utcoffset()), cast("datetime.timedelta", instant.utcoffset())
|
|
45
|
+
return held == read or (held < read) is (value < other)
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def _written_to(form: str, value: datetime.datetime, instant: datetime.datetime) -> tuple[str, str]:
|
|
49
|
+
"""The two in *form*, with their offsets where they read alike: a wall time of a repeated hour is two moments."""
|
|
50
|
+
first, second = value.strftime(form), instant.strftime(form)
|
|
51
|
+
if first != second:
|
|
52
|
+
return first, second
|
|
53
|
+
return first + _offset_of(value), second + _offset_of(instant)
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def _offset_of(value: datetime.datetime) -> str:
|
|
57
|
+
written = value.strftime("%z")
|
|
58
|
+
return f"{written[:3]}:{written[3:]}"
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def _require_comparable_datetimes(first: datetime.datetime, second: datetime.datetime) -> None:
|
|
62
|
+
"""Reject a naive-vs-aware pair with a clear message.
|
|
63
|
+
|
|
64
|
+
The relational assertions would otherwise leak the raw ``TypeError`` from ``<``/``>``. The
|
|
65
|
+
``ignoring_*`` ones compare wall-clock fields, which silently answers a different question: two
|
|
66
|
+
instants hours apart read equal. Both operands are already validated as `datetime.datetime`.
|
|
67
|
+
"""
|
|
68
|
+
if (first.utcoffset() is None) != (second.utcoffset() is None):
|
|
69
|
+
raise TypeError(
|
|
70
|
+
"cannot compare a timezone-naive datetime with a timezone-aware one. Make both aware or both naive first"
|
|
71
|
+
)
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
class DateMixin(_MixinBase):
|
|
75
|
+
"""Date and time assertions mixin."""
|
|
76
|
+
|
|
77
|
+
def is_before(self, other: datetime.datetime) -> Self:
|
|
78
|
+
"""Asserts that val is a date and is before other date.
|
|
79
|
+
|
|
80
|
+
Args:
|
|
81
|
+
other (object): the other date, expected to be after val
|
|
82
|
+
|
|
83
|
+
Examples:
|
|
84
|
+
Usage:
|
|
85
|
+
|
|
86
|
+
import datetime
|
|
87
|
+
|
|
88
|
+
today = datetime.datetime.now()
|
|
89
|
+
yesterday = today - datetime.timedelta(days=1)
|
|
90
|
+
|
|
91
|
+
assert_that(yesterday).is_before(today)
|
|
92
|
+
|
|
93
|
+
Returns:
|
|
94
|
+
AssertionBuilder: returns this instance to chain to the next assertion
|
|
95
|
+
|
|
96
|
+
Raises:
|
|
97
|
+
AssertionError: if val is **not** before the given date
|
|
98
|
+
|
|
99
|
+
See Also:
|
|
100
|
+
[`is_less_than()`][assertpy.numeric.NumericMixin.is_less_than] - numeric assertion, but
|
|
101
|
+
also works with datetime
|
|
102
|
+
[`is_less_than_or_equal_to()`][assertpy.numeric.NumericMixin.is_less_than_or_equal_to] -
|
|
103
|
+
also works with datetime
|
|
104
|
+
"""
|
|
105
|
+
_require_datetime(self.val, "val")
|
|
106
|
+
_require_datetime(other, argument("other"))
|
|
107
|
+
_require_comparable_datetimes(self.val, other)
|
|
108
|
+
if self.val >= other:
|
|
109
|
+
return self.error(
|
|
110
|
+
f"Expected <{_capped(self.val)}> to be before <{_capped_format(other)}>, but was not."
|
|
111
|
+
f"{under(out_of_order(self.val, other, before=True, strict=True))}",
|
|
112
|
+
expected=other,
|
|
113
|
+
)
|
|
114
|
+
return self
|
|
115
|
+
|
|
116
|
+
def is_after(self, other: datetime.datetime) -> Self:
|
|
117
|
+
"""Asserts that val is a date and is after other date.
|
|
118
|
+
|
|
119
|
+
Args:
|
|
120
|
+
other (object): the other date, expected to be before val
|
|
121
|
+
|
|
122
|
+
Examples:
|
|
123
|
+
Usage:
|
|
124
|
+
|
|
125
|
+
import datetime
|
|
126
|
+
|
|
127
|
+
today = datetime.datetime.now()
|
|
128
|
+
yesterday = today - datetime.timedelta(days=1)
|
|
129
|
+
|
|
130
|
+
assert_that(today).is_after(yesterday)
|
|
131
|
+
|
|
132
|
+
Returns:
|
|
133
|
+
AssertionBuilder: returns this instance to chain to the next assertion
|
|
134
|
+
|
|
135
|
+
Raises:
|
|
136
|
+
AssertionError: if val is **not** after the given date
|
|
137
|
+
|
|
138
|
+
See Also:
|
|
139
|
+
[`is_greater_than()`][assertpy.numeric.NumericMixin.is_greater_than] - numeric assertion,
|
|
140
|
+
but also works with datetime
|
|
141
|
+
[`is_greater_than_or_equal_to()`][assertpy.numeric.NumericMixin.is_greater_than_or_equal_to] -
|
|
142
|
+
also works with datetime
|
|
143
|
+
"""
|
|
144
|
+
_require_datetime(self.val, "val")
|
|
145
|
+
_require_datetime(other, argument("other"))
|
|
146
|
+
_require_comparable_datetimes(self.val, other)
|
|
147
|
+
if self.val <= other:
|
|
148
|
+
return self.error(
|
|
149
|
+
f"Expected <{_capped(self.val)}> to be after <{_capped_format(other)}>, but was not."
|
|
150
|
+
f"{under(out_of_order(self.val, other, before=False, strict=True))}",
|
|
151
|
+
expected=other,
|
|
152
|
+
)
|
|
153
|
+
return self
|
|
154
|
+
|
|
155
|
+
def is_before_or_equal_to(self, other: datetime.datetime) -> Self:
|
|
156
|
+
"""Asserts that val is a date and is before or equal to other date.
|
|
157
|
+
|
|
158
|
+
Args:
|
|
159
|
+
other (object): the other date, expected to be after or equal to val
|
|
160
|
+
|
|
161
|
+
Examples:
|
|
162
|
+
Usage:
|
|
163
|
+
|
|
164
|
+
import datetime
|
|
165
|
+
|
|
166
|
+
today = datetime.datetime.now()
|
|
167
|
+
yesterday = today - datetime.timedelta(days=1)
|
|
168
|
+
|
|
169
|
+
assert_that(yesterday).is_before_or_equal_to(today)
|
|
170
|
+
assert_that(today).is_before_or_equal_to(today)
|
|
171
|
+
|
|
172
|
+
Returns:
|
|
173
|
+
AssertionBuilder: returns this instance to chain to the next assertion
|
|
174
|
+
|
|
175
|
+
Raises:
|
|
176
|
+
AssertionError: if val is **not** before or equal to the given date
|
|
177
|
+
"""
|
|
178
|
+
_require_datetime(self.val, "val")
|
|
179
|
+
_require_datetime(other, argument("other"))
|
|
180
|
+
_require_comparable_datetimes(self.val, other)
|
|
181
|
+
if self.val > other:
|
|
182
|
+
return self.error(
|
|
183
|
+
f"Expected <{_capped(self.val)}> to be before or equal to <{_capped_format(other)}>, but was not."
|
|
184
|
+
f"{under(out_of_order(self.val, other, before=True, strict=False))}",
|
|
185
|
+
expected=other,
|
|
186
|
+
)
|
|
187
|
+
return self
|
|
188
|
+
|
|
189
|
+
def is_after_or_equal_to(self, other: datetime.datetime) -> Self:
|
|
190
|
+
"""Asserts that val is a date and is after or equal to other date.
|
|
191
|
+
|
|
192
|
+
Args:
|
|
193
|
+
other (object): the other date, expected to be before or equal to val
|
|
194
|
+
|
|
195
|
+
Examples:
|
|
196
|
+
Usage:
|
|
197
|
+
|
|
198
|
+
import datetime
|
|
199
|
+
|
|
200
|
+
today = datetime.datetime.now()
|
|
201
|
+
yesterday = today - datetime.timedelta(days=1)
|
|
202
|
+
|
|
203
|
+
assert_that(today).is_after_or_equal_to(yesterday)
|
|
204
|
+
assert_that(today).is_after_or_equal_to(today)
|
|
205
|
+
|
|
206
|
+
Returns:
|
|
207
|
+
AssertionBuilder: returns this instance to chain to the next assertion
|
|
208
|
+
|
|
209
|
+
Raises:
|
|
210
|
+
AssertionError: if val is **not** after or equal to the given date
|
|
211
|
+
"""
|
|
212
|
+
_require_datetime(self.val, "val")
|
|
213
|
+
_require_datetime(other, argument("other"))
|
|
214
|
+
_require_comparable_datetimes(self.val, other)
|
|
215
|
+
if self.val < other:
|
|
216
|
+
return self.error(
|
|
217
|
+
f"Expected <{_capped(self.val)}> to be after or equal to <{_capped_format(other)}>, but was not."
|
|
218
|
+
f"{under(out_of_order(self.val, other, before=False, strict=False))}",
|
|
219
|
+
expected=other,
|
|
220
|
+
)
|
|
221
|
+
return self
|
|
222
|
+
|
|
223
|
+
def is_equal_to_ignoring_milliseconds(self, other: datetime.datetime) -> Self:
|
|
224
|
+
"""Asserts that val is a date and is equal to other date to the second.
|
|
225
|
+
|
|
226
|
+
Args:
|
|
227
|
+
other (object): the other date, expected to be equal to the second
|
|
228
|
+
|
|
229
|
+
Examples:
|
|
230
|
+
Usage:
|
|
231
|
+
|
|
232
|
+
import datetime
|
|
233
|
+
|
|
234
|
+
d1 = datetime.datetime(2020, 1, 2, 3, 4, 5, 6) # 2020-01-02 03:04:05.000006
|
|
235
|
+
d2 = datetime.datetime(2020, 1, 2, 3, 4, 5, 777777) # 2020-01-02 03:04:05.777777
|
|
236
|
+
|
|
237
|
+
assert_that(d1).is_equal_to_ignoring_milliseconds(d2)
|
|
238
|
+
|
|
239
|
+
Returns:
|
|
240
|
+
AssertionBuilder: returns this instance to chain to the next assertion
|
|
241
|
+
|
|
242
|
+
Raises:
|
|
243
|
+
AssertionError: if val is **not** equal to the given date to the second
|
|
244
|
+
"""
|
|
245
|
+
_require_datetime(self.val, "val")
|
|
246
|
+
_require_datetime(other, argument("other"))
|
|
247
|
+
_require_comparable_datetimes(self.val, other)
|
|
248
|
+
instant = _read_in_the_zone_of(other, self.val)
|
|
249
|
+
if (
|
|
250
|
+
self.val.date() == instant.date()
|
|
251
|
+
and self.val.hour == instant.hour
|
|
252
|
+
and self.val.minute == instant.minute
|
|
253
|
+
and self.val.second == instant.second
|
|
254
|
+
and (instant is other or _one_reading(self.val, instant, other))
|
|
255
|
+
):
|
|
256
|
+
return self
|
|
257
|
+
read, wanted = _written_to("%Y-%m-%d %H:%M:%S", self.val, instant)
|
|
258
|
+
return self.error(f"Expected <{read}> to be equal to <{wanted}>, but was not.", expected=other)
|
|
259
|
+
|
|
260
|
+
def is_equal_to_ignoring_seconds(self, other: datetime.datetime) -> Self:
|
|
261
|
+
"""Asserts that val is a date and is equal to other date to the minute.
|
|
262
|
+
|
|
263
|
+
Args:
|
|
264
|
+
other (object): the other date, expected to be equal to the minute
|
|
265
|
+
|
|
266
|
+
Examples:
|
|
267
|
+
Usage:
|
|
268
|
+
|
|
269
|
+
import datetime
|
|
270
|
+
|
|
271
|
+
d1 = datetime.datetime(2020, 1, 2, 3, 4, 5) # 2020-01-02 03:04:05
|
|
272
|
+
d2 = datetime.datetime(2020, 1, 2, 3, 4, 55) # 2020-01-02 03:04:55
|
|
273
|
+
|
|
274
|
+
assert_that(d1).is_equal_to_ignoring_seconds(d2)
|
|
275
|
+
|
|
276
|
+
Returns:
|
|
277
|
+
AssertionBuilder: returns this instance to chain to the next assertion
|
|
278
|
+
|
|
279
|
+
Raises:
|
|
280
|
+
AssertionError: if val is **not** equal to the given date to the minute
|
|
281
|
+
"""
|
|
282
|
+
_require_datetime(self.val, "val")
|
|
283
|
+
_require_datetime(other, argument("other"))
|
|
284
|
+
_require_comparable_datetimes(self.val, other)
|
|
285
|
+
instant = _read_in_the_zone_of(other, self.val)
|
|
286
|
+
if (
|
|
287
|
+
self.val.date() == instant.date()
|
|
288
|
+
and self.val.hour == instant.hour
|
|
289
|
+
and self.val.minute == instant.minute
|
|
290
|
+
and (instant is other or _one_reading(self.val, instant, other))
|
|
291
|
+
):
|
|
292
|
+
return self
|
|
293
|
+
read, wanted = _written_to("%Y-%m-%d %H:%M", self.val, instant)
|
|
294
|
+
return self.error(f"Expected <{read}> to be equal to <{wanted}>, but was not.", expected=other)
|
|
295
|
+
|
|
296
|
+
def is_equal_to_ignoring_time(self, other: datetime.datetime) -> Self:
|
|
297
|
+
"""Asserts that val is a date and is equal to other date ignoring time.
|
|
298
|
+
|
|
299
|
+
Args:
|
|
300
|
+
other (object): the other date, expected to be equal ignoring time
|
|
301
|
+
|
|
302
|
+
Examples:
|
|
303
|
+
Usage:
|
|
304
|
+
|
|
305
|
+
import datetime
|
|
306
|
+
|
|
307
|
+
d1 = datetime.datetime(2020, 1, 2, 3, 4, 5) # 2020-01-02 03:04:05
|
|
308
|
+
d2 = datetime.datetime(2020, 1, 2, 13, 44, 55) # 2020-01-02 13:44:55
|
|
309
|
+
|
|
310
|
+
assert_that(d1).is_equal_to_ignoring_time(d2)
|
|
311
|
+
|
|
312
|
+
Returns:
|
|
313
|
+
AssertionBuilder: returns this instance to chain to the next assertion
|
|
314
|
+
|
|
315
|
+
Raises:
|
|
316
|
+
AssertionError: if val is **not** equal to the given date ignoring time
|
|
317
|
+
"""
|
|
318
|
+
_require_datetime(self.val, "val")
|
|
319
|
+
_require_datetime(other, argument("other"))
|
|
320
|
+
_require_comparable_datetimes(self.val, other)
|
|
321
|
+
instant = _read_in_the_zone_of(other, self.val)
|
|
322
|
+
if self.val.date() == instant.date():
|
|
323
|
+
return self
|
|
324
|
+
return self.error(
|
|
325
|
+
f"Expected <{self.val.strftime('%Y-%m-%d')}> to be equal to <{instant.strftime('%Y-%m-%d')}>, but was not.",
|
|
326
|
+
expected=other,
|
|
327
|
+
)
|