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/outcome.py
ADDED
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
"""The record of one failed assertion, between composing it and doing something about it.
|
|
2
|
+
|
|
3
|
+
`AssertionBuilder.error()` used to do both jobs in one body: it built the message, and then it decided
|
|
4
|
+
whether to raise, collect or log. Only the string survived that function, which is why the soft
|
|
5
|
+
collector can hold nothing else, why there is nowhere to hand a caller a result instead of an
|
|
6
|
+
exception, and why `eventually()` has to bypass `error()` entirely to attach its poll trace.
|
|
7
|
+
|
|
8
|
+
Splitting the two leaves a record in the middle. This is that record. It carries what was composed,
|
|
9
|
+
not what should happen to it: the decision belongs to the delivery half, which reads the builder's
|
|
10
|
+
mode.
|
|
11
|
+
|
|
12
|
+
`check()` is the first caller outside this package to read one, so the type is exported and documented
|
|
13
|
+
from here on. What it does **not** promise yet is that no field will be added: the soft collector is
|
|
14
|
+
next to hold these, and holding them is what will tell whether a failure's location belongs on the
|
|
15
|
+
record too.
|
|
16
|
+
"""
|
|
17
|
+
|
|
18
|
+
from __future__ import annotations
|
|
19
|
+
|
|
20
|
+
from dataclasses import dataclass, field
|
|
21
|
+
from typing import TYPE_CHECKING, Final
|
|
22
|
+
|
|
23
|
+
if TYPE_CHECKING:
|
|
24
|
+
from collections.abc import Mapping
|
|
25
|
+
|
|
26
|
+
from .errors import DiffResult, PollTrace
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
class _Missing:
|
|
30
|
+
"""The type of `MISSING`, so it reprs as itself in a dataclass and in a debugger."""
|
|
31
|
+
|
|
32
|
+
__slots__ = ()
|
|
33
|
+
|
|
34
|
+
def __repr__(self) -> str:
|
|
35
|
+
return "MISSING"
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
MISSING: Final = _Missing()
|
|
39
|
+
"""Stands in for an argument the caller did not pass.
|
|
40
|
+
|
|
41
|
+
``None`` cannot do that job: an assertion comparing a value against ``None`` passes ``None`` as a real
|
|
42
|
+
expected value, and the two readings are then indistinguishable. The same ambiguity in `DiffEntry`
|
|
43
|
+
used to render a key whose value is ``None`` as a key that is not there at all.
|
|
44
|
+
"""
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
@dataclass(frozen=True, slots=True)
|
|
48
|
+
class Requirement:
|
|
49
|
+
"""What was asked of the value, as data rather than as a sentence.
|
|
50
|
+
|
|
51
|
+
A consumer reading `AssertionOutcome` could tell what failed only by parsing the message: the
|
|
52
|
+
fields beside it answer what the values were, not what was asked of them. `expected` carries five
|
|
53
|
+
different things depending on the family, the operand for `is_equal_to(2)`, a tuple for
|
|
54
|
+
`contains(2)`, a rendered description for `satisfies(...)`, a type for `is_instance_of(str)`, and
|
|
55
|
+
nothing at all for `is_empty()`, so grouping failures across a suite meant reading English.
|
|
56
|
+
"""
|
|
57
|
+
|
|
58
|
+
operation: str
|
|
59
|
+
"""The name the caller wrote, as it appears in the API.
|
|
60
|
+
|
|
61
|
+
A delegating assertion answers its own name rather than the one it delegates to: `is_positive()`
|
|
62
|
+
asks `is_greater_than(0)` underneath, and the reader wants the assertion in the test.
|
|
63
|
+
"""
|
|
64
|
+
|
|
65
|
+
parameters: Mapping[str, object] = field(default_factory=dict)
|
|
66
|
+
"""Every declared parameter with the value the assertion ran with, by name.
|
|
67
|
+
|
|
68
|
+
Bound values rather than the call as written, which means a parameter left out appears with its
|
|
69
|
+
default. Two spellings of one requirement then group as one: `is_close_to(9, 0.1)` and
|
|
70
|
+
`is_close_to(other=9, tolerance=0.1)` both read ``{"other": 9, "tolerance": 0.1}``, where a
|
|
71
|
+
positional tuple would also be indistinguishable from a two-operand `contains`.
|
|
72
|
+
|
|
73
|
+
The values are the objects themselves, as `actual` and `expected` already are, so a consumer that
|
|
74
|
+
serialises has the same work to do here as there.
|
|
75
|
+
"""
|
|
76
|
+
|
|
77
|
+
negated: bool = False
|
|
78
|
+
"""Whether the assertion ran through `not_`, where holding is the failure."""
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
@dataclass(frozen=True, slots=True, kw_only=True)
|
|
82
|
+
class AssertionOutcome:
|
|
83
|
+
"""What one assertion decided, as a value rather than as a raised exception.
|
|
84
|
+
|
|
85
|
+
Returned by [`check()`][assertpy.assertpy.AssertionBuilder.check], which runs an assertion for its
|
|
86
|
+
verdict instead of for its failure. Truthy when the assertion passed, so it reads as the answer to
|
|
87
|
+
the question it was asked.
|
|
88
|
+
"""
|
|
89
|
+
|
|
90
|
+
passed: bool = False
|
|
91
|
+
"""Whether the assertion held.
|
|
92
|
+
|
|
93
|
+
``False`` on every record the failure path builds, which is all of them until something asks for a
|
|
94
|
+
verdict: a failure is composed only when there is one.
|
|
95
|
+
"""
|
|
96
|
+
|
|
97
|
+
message: str = ""
|
|
98
|
+
"""The full failure text, description prefix and all, exactly as it reaches the reader.
|
|
99
|
+
|
|
100
|
+
Empty on a passing outcome. There is no message for an assertion that held, and inventing one
|
|
101
|
+
would put words in the report of anything that logs whatever it is handed.
|
|
102
|
+
"""
|
|
103
|
+
|
|
104
|
+
actual: object = None
|
|
105
|
+
"""The value under test. Filled from the builder when the assertion did not name one itself, so
|
|
106
|
+
every failure carries it, and `actual_provided` says which of the two happened."""
|
|
107
|
+
|
|
108
|
+
actual_provided: bool = False
|
|
109
|
+
"""Whether the assertion passed ``actual`` itself rather than having it filled in.
|
|
110
|
+
|
|
111
|
+
Read by anything that renders: a value the assertion chose to name is worth showing, one filled in
|
|
112
|
+
from the builder is usually already in the message.
|
|
113
|
+
"""
|
|
114
|
+
|
|
115
|
+
expected: object = MISSING
|
|
116
|
+
diff: DiffResult | None = None
|
|
117
|
+
|
|
118
|
+
requirement: Requirement | None = None
|
|
119
|
+
"""Which operation was asked, with which parameters, and whether it was negated.
|
|
120
|
+
|
|
121
|
+
``None`` where no operation was asked, and that is a limit of the contract rather than a gap to be
|
|
122
|
+
closed later. Three shapes have no operation to name: `fail()`, a bare `error()` carrying a
|
|
123
|
+
message of the caller's own, and a precondition of one of the few members `_operations.py` records
|
|
124
|
+
as asserting nothing, where the failure is about the value's shape and not about a requirement.
|
|
125
|
+
"""
|
|
126
|
+
|
|
127
|
+
trace: PollTrace | None = None
|
|
128
|
+
"""The convergence telemetry of a poll that timed out.
|
|
129
|
+
|
|
130
|
+
Here rather than only on the exception because a polling assertion under a soft block or in warn
|
|
131
|
+
mode never builds one: it goes through the same delivery as everything else, and the trace used to
|
|
132
|
+
stop at that boundary.
|
|
133
|
+
"""
|
|
134
|
+
|
|
135
|
+
group: str | None = None
|
|
136
|
+
"""The label a soft block was grouping under when this was collected.
|
|
137
|
+
|
|
138
|
+
Only a soft block groups, so this is ``None`` everywhere else, including on a failure that was
|
|
139
|
+
raised. Kept on the record rather than beside it so a collected failure stays one thing.
|
|
140
|
+
"""
|
|
141
|
+
|
|
142
|
+
location: tuple[str, int] | None = None
|
|
143
|
+
"""The ``(file, line)`` of the caller, on a failure that was collected rather than raised.
|
|
144
|
+
|
|
145
|
+
``None`` on a raised failure, whose traceback is the better answer, and where finding this costs a
|
|
146
|
+
walk of the whole stack that nothing would read.
|
|
147
|
+
"""
|
|
148
|
+
|
|
149
|
+
hint: str | None = None
|
|
150
|
+
"""The diagnostic line, kept apart from ``message`` as well as glued into it.
|
|
151
|
+
|
|
152
|
+
It is glued in because that is where a reader needs it, and kept apart because once it is part of
|
|
153
|
+
the string nothing downstream can tell it from the assertion's own words.
|
|
154
|
+
"""
|
|
155
|
+
|
|
156
|
+
@property
|
|
157
|
+
def has_expected(self) -> bool:
|
|
158
|
+
"""Whether an expected value was named at all, which ``expected is not None`` cannot answer."""
|
|
159
|
+
return self.expected is not MISSING
|
|
160
|
+
|
|
161
|
+
def __bool__(self) -> bool:
|
|
162
|
+
"""The verdict, so ``if assert_that(x).check().is_positive():`` reads as the question it asks."""
|
|
163
|
+
return self.passed
|
assertpy/py.typed
ADDED
|
File without changes
|