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.
Files changed (63) hide show
  1. assertpy/__init__.py +87 -0
  2. assertpy/_clustering.py +511 -0
  3. assertpy/_dangling.py +505 -0
  4. assertpy/_engine/__init__.py +1 -0
  5. assertpy/_engine/_builder_check_typing.py +1365 -0
  6. assertpy/_engine/_capable_typing.py +772 -0
  7. assertpy/_engine/_check_typing.py +753 -0
  8. assertpy/_engine/_compare.py +983 -0
  9. assertpy/_engine/_compat.py +38 -0
  10. assertpy/_engine/_contract.py +1926 -0
  11. assertpy/_engine/_diff.py +1058 -0
  12. assertpy/_engine/_equality.py +843 -0
  13. assertpy/_engine/_introspection.py +729 -0
  14. assertpy/_engine/_membership.py +522 -0
  15. assertpy/_engine/_mixin_base.py +258 -0
  16. assertpy/_engine/_negated_typing.py +2176 -0
  17. assertpy/_engine/_operations.py +75 -0
  18. assertpy/_engine/_ordering.py +884 -0
  19. assertpy/_engine/_pairing.py +70 -0
  20. assertpy/_engine/_path.py +158 -0
  21. assertpy/_engine/_poll_typing.py +6039 -0
  22. assertpy/_engine/_require.py +312 -0
  23. assertpy/_engine/_size.py +31 -0
  24. assertpy/_engine/_text.py +39 -0
  25. assertpy/_engine/_typing.py +1226 -0
  26. assertpy/_hints.py +1213 -0
  27. assertpy/_inline.py +484 -0
  28. assertpy/_matcher_impls.py +1876 -0
  29. assertpy/_passes.py +249 -0
  30. assertpy/_renamed.py +64 -0
  31. assertpy/_satisfies.py +835 -0
  32. assertpy/_snapshot_codec.py +290 -0
  33. assertpy/assertpy.py +2640 -0
  34. assertpy/async_assertions.py +864 -0
  35. assertpy/base.py +862 -0
  36. assertpy/behave_matchers.py +96 -0
  37. assertpy/bytes_mixin.py +158 -0
  38. assertpy/collection.py +528 -0
  39. assertpy/contains.py +1068 -0
  40. assertpy/dataframe.py +200 -0
  41. assertpy/date.py +327 -0
  42. assertpy/dict.py +249 -0
  43. assertpy/dynamic.py +222 -0
  44. assertpy/errors.py +782 -0
  45. assertpy/exception.py +838 -0
  46. assertpy/extracting.py +266 -0
  47. assertpy/file.py +324 -0
  48. assertpy/helpers.py +747 -0
  49. assertpy/http_mixin.py +218 -0
  50. assertpy/json_mixin.py +1265 -0
  51. assertpy/matchers.py +639 -0
  52. assertpy/numeric.py +696 -0
  53. assertpy/outcome.py +163 -0
  54. assertpy/py.typed +0 -0
  55. assertpy/pytest_plugin.py +1650 -0
  56. assertpy/snapshot.py +1031 -0
  57. assertpy/string.py +803 -0
  58. assertpy/warning.py +137 -0
  59. assertpy-2.35.0.dist-info/METADATA +324 -0
  60. assertpy-2.35.0.dist-info/RECORD +63 -0
  61. assertpy-2.35.0.dist-info/WHEEL +4 -0
  62. assertpy-2.35.0.dist-info/entry_points.txt +2 -0
  63. 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)
@@ -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)