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
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from types import MappingProxyType
|
|
4
|
+
from typing import TYPE_CHECKING, Any, Final, TypeVar, cast
|
|
5
|
+
|
|
6
|
+
if TYPE_CHECKING:
|
|
7
|
+
from collections.abc import Callable, Mapping
|
|
8
|
+
|
|
9
|
+
_ParserT = TypeVar("_ParserT", bound="Callable[[str], Any]")
|
|
10
|
+
|
|
11
|
+
_BOOL_TRUE: Final = frozenset({"true", "yes", "1", "on"})
|
|
12
|
+
_BOOL_FALSE: Final = frozenset({"false", "no", "0", "off"})
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def _with_pattern(pattern: str) -> Callable[[_ParserT], _ParserT]:
|
|
16
|
+
def decorator(func: _ParserT) -> _ParserT:
|
|
17
|
+
# behave reads the pattern off the function object, which is not something a callable type carries
|
|
18
|
+
cast("Any", func).pattern = pattern
|
|
19
|
+
return func
|
|
20
|
+
|
|
21
|
+
return decorator
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
@_with_pattern(r"\d+")
|
|
25
|
+
def _positive_int(text: str) -> int:
|
|
26
|
+
value = int(text)
|
|
27
|
+
if value <= 0:
|
|
28
|
+
raise ValueError(f"expected positive integer, got {value}")
|
|
29
|
+
return value
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
@_with_pattern(r"\d+")
|
|
33
|
+
def _non_negative_int(text: str) -> int:
|
|
34
|
+
return int(text)
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
@_with_pattern(r"\d+\.?\d*")
|
|
38
|
+
def _positive_float(text: str) -> float:
|
|
39
|
+
value = float(text)
|
|
40
|
+
if value <= 0:
|
|
41
|
+
raise ValueError(f"expected positive float, got {value}")
|
|
42
|
+
return value
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
@_with_pattern(r".+?")
|
|
46
|
+
def _non_empty_string(text: str) -> str:
|
|
47
|
+
stripped = text.strip()
|
|
48
|
+
if not stripped:
|
|
49
|
+
raise ValueError("expected non-empty string, got blank")
|
|
50
|
+
return stripped
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
@_with_pattern(r"\w+")
|
|
54
|
+
def _bool_like(text: str) -> bool:
|
|
55
|
+
lower = text.strip().lower()
|
|
56
|
+
if lower in _BOOL_TRUE:
|
|
57
|
+
return True
|
|
58
|
+
if lower in _BOOL_FALSE:
|
|
59
|
+
return False
|
|
60
|
+
raise ValueError(f"expected boolean-like value, got {text!r}")
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
ASSERTPY_TYPES: Final[Mapping[str, Callable[[str], Any]]] = MappingProxyType(
|
|
64
|
+
{
|
|
65
|
+
"PositiveInt": _positive_int,
|
|
66
|
+
"NonNegativeInt": _non_negative_int,
|
|
67
|
+
"PositiveFloat": _positive_float,
|
|
68
|
+
"NonEmptyString": _non_empty_string,
|
|
69
|
+
"BoolLike": _bool_like,
|
|
70
|
+
}
|
|
71
|
+
)
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
def register_assertpy_types() -> None:
|
|
75
|
+
"""Register assertpy parameter types for Behave step definitions.
|
|
76
|
+
|
|
77
|
+
Registers the following types for use in step patterns:
|
|
78
|
+
|
|
79
|
+
- ``{param:PositiveInt}`` - positive integer (> 0)
|
|
80
|
+
- ``{param:NonNegativeInt}`` - non-negative integer (>= 0)
|
|
81
|
+
- ``{param:PositiveFloat}`` - positive float (> 0)
|
|
82
|
+
- ``{param:NonEmptyString}`` - non-empty, stripped string
|
|
83
|
+
- ``{param:BoolLike}`` - boolean from true/false/yes/no/1/0/on/off
|
|
84
|
+
|
|
85
|
+
Requires ``behave`` to be installed (``pip install assertpy[behave]``).
|
|
86
|
+
|
|
87
|
+
Raises:
|
|
88
|
+
ImportError: if behave is not installed
|
|
89
|
+
"""
|
|
90
|
+
try:
|
|
91
|
+
from behave import register_type # ty: ignore[unresolved-import] # optional dependency
|
|
92
|
+
except ImportError:
|
|
93
|
+
raise ImportError(
|
|
94
|
+
"behave is required for register_assertpy_types(). Install it with: pip install assertpy[behave]"
|
|
95
|
+
) from None
|
|
96
|
+
register_type(**ASSERTPY_TYPES)
|
assertpy/bytes_mixin.py
ADDED
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from typing import TYPE_CHECKING, SupportsIndex
|
|
4
|
+
|
|
5
|
+
from ._engine._mixin_base import _MixinBase
|
|
6
|
+
from ._engine._ordering import require_integer
|
|
7
|
+
from ._engine._require import refuse, sized_len
|
|
8
|
+
|
|
9
|
+
if TYPE_CHECKING:
|
|
10
|
+
from ._engine._compat import Self
|
|
11
|
+
|
|
12
|
+
__tracebackhide__ = True
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class BytesMixin(_MixinBase):
|
|
16
|
+
"""Assertions for bytes and bytearray values."""
|
|
17
|
+
|
|
18
|
+
def _check_bytes(self) -> None:
|
|
19
|
+
if not isinstance(self.val, (bytes, bytearray)):
|
|
20
|
+
refuse(self.val, "bytes or a bytearray")
|
|
21
|
+
|
|
22
|
+
def is_valid_utf8(self) -> Self:
|
|
23
|
+
"""Assert that val is valid UTF-8.
|
|
24
|
+
|
|
25
|
+
Returns:
|
|
26
|
+
AssertionBuilder: returns this instance to chain to the next assertion
|
|
27
|
+
|
|
28
|
+
Raises:
|
|
29
|
+
AssertionError: if val is not valid UTF-8
|
|
30
|
+
"""
|
|
31
|
+
self._check_bytes()
|
|
32
|
+
try:
|
|
33
|
+
self.val.decode("utf-8")
|
|
34
|
+
except UnicodeDecodeError:
|
|
35
|
+
return self.error("Expected valid UTF-8, but decoding failed.")
|
|
36
|
+
return self
|
|
37
|
+
|
|
38
|
+
def is_valid_encoding(self, encoding: str) -> Self:
|
|
39
|
+
"""Assert that val is valid in the given encoding.
|
|
40
|
+
|
|
41
|
+
Args:
|
|
42
|
+
encoding: the encoding to validate against (e.g. ``"ascii"``, ``"utf-16"``).
|
|
43
|
+
|
|
44
|
+
Returns:
|
|
45
|
+
AssertionBuilder: returns this instance to chain to the next assertion
|
|
46
|
+
|
|
47
|
+
Raises:
|
|
48
|
+
AssertionError: if val cannot be decoded with the given encoding
|
|
49
|
+
LookupError: if there is no such codec, which is a mistake in the call rather than a verdict
|
|
50
|
+
about the value: reported as a failure, ``not_`` and ``check()`` said the value was fine
|
|
51
|
+
"""
|
|
52
|
+
self._check_bytes()
|
|
53
|
+
try:
|
|
54
|
+
self.val.decode(encoding)
|
|
55
|
+
except UnicodeDecodeError:
|
|
56
|
+
return self.error(f"Expected valid {encoding} encoding, but decoding failed.", expected=encoding)
|
|
57
|
+
return self
|
|
58
|
+
|
|
59
|
+
def starts_with_bytes(self, prefix: bytes | bytearray) -> Self:
|
|
60
|
+
"""Assert that val starts with the given byte prefix.
|
|
61
|
+
|
|
62
|
+
The bytes-only spelling of [`starts_with()`][assertpy.string.StringMixin.starts_with], which it
|
|
63
|
+
delegates to. It carried its own implementation until `starts_with` learned to handle ``bytes``:
|
|
64
|
+
before that, a byte string fell through to the element-wise branch and ``b"foo"`` compared its
|
|
65
|
+
first element, the int ``102``, against ``b"f"``. Two implementations of one relation is how they
|
|
66
|
+
drift, and these two already had: this one accepted an empty prefix, which asserts nothing.
|
|
67
|
+
|
|
68
|
+
Args:
|
|
69
|
+
prefix: the expected byte prefix.
|
|
70
|
+
|
|
71
|
+
Returns:
|
|
72
|
+
AssertionBuilder: returns this instance to chain to the next assertion
|
|
73
|
+
|
|
74
|
+
Raises:
|
|
75
|
+
TypeError: if val is not ``bytes``/``bytearray``
|
|
76
|
+
ValueError: if the prefix is empty, which no value can fail
|
|
77
|
+
AssertionError: if val does not start with the prefix
|
|
78
|
+
"""
|
|
79
|
+
self._check_bytes()
|
|
80
|
+
return self.starts_with(prefix)
|
|
81
|
+
|
|
82
|
+
def contains_bytes(self, sub: bytes | bytearray) -> Self:
|
|
83
|
+
"""Assert that val contains the given byte subsequence.
|
|
84
|
+
|
|
85
|
+
The bytes-only spelling of [`contains()`][assertpy.contains.ContainsMixin.contains], which it
|
|
86
|
+
delegates to. Delegating is what gives the failure the value under test and a structured diff,
|
|
87
|
+
neither of which its own implementation produced.
|
|
88
|
+
|
|
89
|
+
Args:
|
|
90
|
+
sub: the byte subsequence to find.
|
|
91
|
+
|
|
92
|
+
Returns:
|
|
93
|
+
AssertionBuilder: returns this instance to chain to the next assertion
|
|
94
|
+
|
|
95
|
+
Raises:
|
|
96
|
+
TypeError: if val is not ``bytes``/``bytearray``
|
|
97
|
+
AssertionError: if val does not contain the subsequence
|
|
98
|
+
"""
|
|
99
|
+
self._check_bytes()
|
|
100
|
+
return self.contains(sub)
|
|
101
|
+
|
|
102
|
+
def has_byte_at(self, index: SupportsIndex, expected: SupportsIndex) -> Self:
|
|
103
|
+
"""Assert that the byte at the given index equals the expected value.
|
|
104
|
+
|
|
105
|
+
Args:
|
|
106
|
+
index: zero-based byte index.
|
|
107
|
+
expected: expected byte value (0-255).
|
|
108
|
+
|
|
109
|
+
Returns:
|
|
110
|
+
AssertionBuilder: returns this instance to chain to the next assertion
|
|
111
|
+
|
|
112
|
+
Raises:
|
|
113
|
+
AssertionError: if the byte at index does not match
|
|
114
|
+
"""
|
|
115
|
+
self._check_bytes()
|
|
116
|
+
position = index if type(index) is int else require_integer(index, "index")
|
|
117
|
+
if position < 0 or position >= sized_len(self.val):
|
|
118
|
+
raise IndexError(f"Expected index {index} to be in range [0, {sized_len(self.val)}), but was out of range.")
|
|
119
|
+
actual = self.val[position]
|
|
120
|
+
if actual == expected:
|
|
121
|
+
return self
|
|
122
|
+
return self.error(
|
|
123
|
+
f"Expected byte at index {index} to be <0x{expected:02x}>, but was <0x{actual:02x}>.", expected=expected
|
|
124
|
+
)
|
|
125
|
+
|
|
126
|
+
def is_hex_equal_to(self, expected_hex: str) -> Self:
|
|
127
|
+
"""Assert that val equals the given hex string.
|
|
128
|
+
|
|
129
|
+
Args:
|
|
130
|
+
expected_hex: hex string without prefix (e.g. ``"abcdef"``).
|
|
131
|
+
|
|
132
|
+
Returns:
|
|
133
|
+
AssertionBuilder: returns this instance to chain to the next assertion
|
|
134
|
+
|
|
135
|
+
Raises:
|
|
136
|
+
AssertionError: if val does not match the hex string
|
|
137
|
+
"""
|
|
138
|
+
self._check_bytes()
|
|
139
|
+
expected = bytes.fromhex(expected_hex)
|
|
140
|
+
if self.val == expected:
|
|
141
|
+
return self
|
|
142
|
+
return self.error(f"Expected hex <{expected_hex}>, but was <{self.val.hex()}>.", expected=expected_hex)
|
|
143
|
+
|
|
144
|
+
def decoded_as(self, encoding: str = "utf-8") -> Self:
|
|
145
|
+
"""Decode val and return a new builder with the decoded string.
|
|
146
|
+
|
|
147
|
+
Args:
|
|
148
|
+
encoding: the encoding to use (default ``"utf-8"``).
|
|
149
|
+
|
|
150
|
+
Returns:
|
|
151
|
+
AssertionBuilder: a new instance with the decoded string as val
|
|
152
|
+
|
|
153
|
+
Raises:
|
|
154
|
+
UnicodeDecodeError: if val cannot be decoded
|
|
155
|
+
"""
|
|
156
|
+
self._check_bytes()
|
|
157
|
+
decoded = self.val.decode(encoding)
|
|
158
|
+
return self.builder(decoded, self.description, self.kind, logger=self.logger)
|