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/warning.py
ADDED
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import warnings
|
|
4
|
+
from typing import TYPE_CHECKING, cast
|
|
5
|
+
|
|
6
|
+
from ._engine._mixin_base import _MixinBase
|
|
7
|
+
from ._engine._require import refuse
|
|
8
|
+
from .errors import _callable_name, _type_expression_name
|
|
9
|
+
from .exception import _InertBuilder, _require_synchronous_result, named_classes
|
|
10
|
+
|
|
11
|
+
if TYPE_CHECKING:
|
|
12
|
+
from ._engine._compat import Self
|
|
13
|
+
from ._matcher_impls import WarningInfo
|
|
14
|
+
|
|
15
|
+
__tracebackhide__ = True
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class WarningMixin(_MixinBase):
|
|
19
|
+
"""Expected warning mixin (a ``pytest.warns``-style assertion).
|
|
20
|
+
|
|
21
|
+
The front verbs ([`warns()`][assertpy.warning.WarningMixin.warns] /
|
|
22
|
+
[`does_not_warn()`][assertpy.warning.WarningMixin.does_not_warn]) only record the expected
|
|
23
|
+
warning
|
|
24
|
+
category; the actual invocation happens in
|
|
25
|
+
[`when_called_with()`][assertpy.exception.ExceptionMixin.when_called_with], which dispatches to the private
|
|
26
|
+
handlers below.
|
|
27
|
+
"""
|
|
28
|
+
|
|
29
|
+
def warns(self, warning: type[Warning] | WarningInfo = Warning) -> Self:
|
|
30
|
+
"""Asserts that val is callable and sets the expected warning category.
|
|
31
|
+
|
|
32
|
+
Just records the expectation, but never calls val. You must chain to
|
|
33
|
+
[`when_called_with()`][assertpy.exception.ExceptionMixin.when_called_with] to invoke ``val()`` inside a
|
|
34
|
+
warning-capturing context. On success, the matched warning's message becomes the new val so
|
|
35
|
+
you can chain further assertions on it.
|
|
36
|
+
|
|
37
|
+
Subclasses of the given category match, and the category defaults to the base `Warning`
|
|
38
|
+
(which matches any warning).
|
|
39
|
+
|
|
40
|
+
Args:
|
|
41
|
+
warning: the expected warning category (a `Warning` subclass), or a union or a tuple of
|
|
42
|
+
them any one of which will do
|
|
43
|
+
|
|
44
|
+
Examples:
|
|
45
|
+
Usage:
|
|
46
|
+
|
|
47
|
+
assert_that(deprecated_func).warns(DeprecationWarning).when_called_with("foo")
|
|
48
|
+
assert_that(deprecated_func).warns(DeprecationWarning).when_called_with("foo").matches("since 2.6")
|
|
49
|
+
|
|
50
|
+
Returns:
|
|
51
|
+
AssertionBuilder: returns a new instance (now with the expected warning) to chain to
|
|
52
|
+
[`when_called_with()`][assertpy.exception.ExceptionMixin.when_called_with]
|
|
53
|
+
|
|
54
|
+
Note:
|
|
55
|
+
Capturing warnings mutates process-global state, so this is **not** thread-safe across OS
|
|
56
|
+
threads (the same caveat as ``pytest.warns``). It is safe within a single thread,
|
|
57
|
+
including multiple ``asyncio`` tasks on one event loop.
|
|
58
|
+
"""
|
|
59
|
+
return self._warning_builder(warning)
|
|
60
|
+
|
|
61
|
+
def does_not_warn(self, warning: type[Warning] | WarningInfo = Warning) -> Self:
|
|
62
|
+
"""Asserts that val is callable and sets the not-expected warning category.
|
|
63
|
+
|
|
64
|
+
Just records the expectation, but never calls val. You must chain to
|
|
65
|
+
[`when_called_with()`][assertpy.exception.ExceptionMixin.when_called_with] to invoke ``val()`` and assert
|
|
66
|
+
that no warning of the given category is emitted. The category defaults to the base
|
|
67
|
+
`Warning` (which forbids any warning).
|
|
68
|
+
|
|
69
|
+
Args:
|
|
70
|
+
warning: the warning category that should **not** be emitted (a `Warning` subclass), or a
|
|
71
|
+
union or a tuple of them
|
|
72
|
+
|
|
73
|
+
Examples:
|
|
74
|
+
Usage:
|
|
75
|
+
|
|
76
|
+
assert_that(safe_func).does_not_warn(DeprecationWarning).when_called_with("foo")
|
|
77
|
+
|
|
78
|
+
Returns:
|
|
79
|
+
AssertionBuilder: returns a new instance to chain to
|
|
80
|
+
[`when_called_with()`][assertpy.exception.ExceptionMixin.when_called_with]
|
|
81
|
+
"""
|
|
82
|
+
new_builder = self._warning_builder(warning)
|
|
83
|
+
new_builder._not_expected = True
|
|
84
|
+
return new_builder
|
|
85
|
+
|
|
86
|
+
def _warning_builder(self, warning: type[Warning] | WarningInfo) -> Self:
|
|
87
|
+
"""Validate args and build a new instance carrying the expected warning category."""
|
|
88
|
+
if not callable(self.val):
|
|
89
|
+
refuse(self.val, "callable")
|
|
90
|
+
# every member asked here: `issubclass` on a non-class raises with a message about the builtin instead
|
|
91
|
+
named_classes(warning, Warning, "a warning type, or a union or a non-empty tuple of them", subject="warning")
|
|
92
|
+
new_builder = self.builder(self.val, self.description, self.kind, logger=self.logger)
|
|
93
|
+
new_builder._expected_warning = warning
|
|
94
|
+
return new_builder
|
|
95
|
+
|
|
96
|
+
def _when_called_with_warning(self, expected: type[Warning] | WarningInfo, *some_args, **some_kwargs) -> Self:
|
|
97
|
+
with warnings.catch_warnings(record=True) as caught:
|
|
98
|
+
warnings.simplefilter("always") # bypass __warningregistry__ "show once" dedup and filterwarnings=error
|
|
99
|
+
result = self.val(*some_args, **some_kwargs)
|
|
100
|
+
_require_synchronous_result(result, self.val)
|
|
101
|
+
matched = [warning for warning in caught if issubclass(warning.category, expected)]
|
|
102
|
+
if matched:
|
|
103
|
+
captured = self.builder(str(matched[0].message), self.description, self.kind, logger=self.logger)
|
|
104
|
+
captured._return_value = result
|
|
105
|
+
return captured
|
|
106
|
+
if caught:
|
|
107
|
+
seen = ", ".join(sorted({warning.category.__name__ for warning in caught}))
|
|
108
|
+
self.error(
|
|
109
|
+
f"Expected <{_callable_name(self.val)}> to warn <{_type_expression_name(expected)}>"
|
|
110
|
+
f" when called with ({self._fmt_args_kwargs(*some_args, **some_kwargs)}),"
|
|
111
|
+
f" but warned <{seen}>.",
|
|
112
|
+
expected=expected,
|
|
113
|
+
)
|
|
114
|
+
return cast("Self", _InertBuilder(self._value_taint_reason))
|
|
115
|
+
self.error(
|
|
116
|
+
f"Expected <{_callable_name(self.val)}> to warn <{_type_expression_name(expected)}>"
|
|
117
|
+
f" when called with ({self._fmt_args_kwargs(*some_args, **some_kwargs)}).",
|
|
118
|
+
expected=expected,
|
|
119
|
+
)
|
|
120
|
+
return cast("Self", _InertBuilder(self._value_taint_reason))
|
|
121
|
+
|
|
122
|
+
def _when_called_with_not_warning(self, expected: type[Warning] | WarningInfo, *some_args, **some_kwargs) -> Self:
|
|
123
|
+
with warnings.catch_warnings(record=True) as caught:
|
|
124
|
+
warnings.simplefilter("always") # bypass __warningregistry__ "show once" dedup and filterwarnings=error
|
|
125
|
+
result = self.val(*some_args, **some_kwargs)
|
|
126
|
+
_require_synchronous_result(result, self.val)
|
|
127
|
+
matched = [warning for warning in caught if issubclass(warning.category, expected)]
|
|
128
|
+
if matched:
|
|
129
|
+
seen = ", ".join(sorted({warning.category.__name__ for warning in matched}))
|
|
130
|
+
self.error(
|
|
131
|
+
f"Expected <{_callable_name(self.val)}> to not warn <{_type_expression_name(expected)}>"
|
|
132
|
+
f" when called with ({self._fmt_args_kwargs(*some_args, **some_kwargs)}),"
|
|
133
|
+
f" but did warn <{seen}>."
|
|
134
|
+
)
|
|
135
|
+
return cast("Self", _InertBuilder(self._value_taint_reason))
|
|
136
|
+
self._return_value = result
|
|
137
|
+
return self
|
|
@@ -0,0 +1,324 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: assertpy
|
|
3
|
+
Version: 2.35.0
|
|
4
|
+
Summary: Fluent, fully typed assertion library for Python: your type checker sees only the assertions that fit the value, and every failure carries a structured diff. Soft assertions, polling, inline snapshots, OpenAPI contracts.
|
|
5
|
+
Project-URL: Homepage, https://github.com/assertpy/assertpy
|
|
6
|
+
Project-URL: Repository, https://github.com/assertpy/assertpy
|
|
7
|
+
Project-URL: Issues, https://github.com/assertpy/assertpy/issues
|
|
8
|
+
Author-email: Justin Shacklette <justin@saturnboy.com>
|
|
9
|
+
Maintainer-email: Solganis <solganis.dev@gmail.com>
|
|
10
|
+
License-Expression: BSD-3-Clause
|
|
11
|
+
License-File: LICENSE
|
|
12
|
+
Keywords: assert,assert_that,assertion,assertpy,assertthat,fluent,matchers,pytest,test,testing,type-safe,unittest
|
|
13
|
+
Classifier: Development Status :: 5 - Production/Stable
|
|
14
|
+
Classifier: Intended Audience :: Developers
|
|
15
|
+
Classifier: License :: OSI Approved :: BSD License
|
|
16
|
+
Classifier: Natural Language :: English
|
|
17
|
+
Classifier: Operating System :: OS Independent
|
|
18
|
+
Classifier: Programming Language :: Python
|
|
19
|
+
Classifier: Programming Language :: Python :: 3
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
21
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
22
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
23
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
24
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
25
|
+
Classifier: Programming Language :: Python :: 3.15
|
|
26
|
+
Classifier: Topic :: Software Development
|
|
27
|
+
Classifier: Topic :: Software Development :: Testing
|
|
28
|
+
Requires-Python: >=3.10
|
|
29
|
+
Requires-Dist: typing-extensions>=4.0; python_version < '3.11'
|
|
30
|
+
Provides-Extra: allure
|
|
31
|
+
Requires-Dist: allure-pytest>=2.13; extra == 'allure'
|
|
32
|
+
Provides-Extra: behave
|
|
33
|
+
Requires-Dist: behave>=1.3.0; extra == 'behave'
|
|
34
|
+
Provides-Extra: data
|
|
35
|
+
Requires-Dist: numpy>=1.26; extra == 'data'
|
|
36
|
+
Requires-Dist: pandas>=2.0; extra == 'data'
|
|
37
|
+
Requires-Dist: polars>=1.0; extra == 'data'
|
|
38
|
+
Provides-Extra: inline
|
|
39
|
+
Requires-Dist: asttokens>=2.0.4; extra == 'inline'
|
|
40
|
+
Requires-Dist: executing>=2.2; extra == 'inline'
|
|
41
|
+
Provides-Extra: json
|
|
42
|
+
Requires-Dist: idna>=3.7; extra == 'json'
|
|
43
|
+
Requires-Dist: jsonpath-ng>=1.8; extra == 'json'
|
|
44
|
+
Requires-Dist: jsonschema>=4.18; extra == 'json'
|
|
45
|
+
Requires-Dist: referencing>=0.28.4; extra == 'json'
|
|
46
|
+
Provides-Extra: numpy
|
|
47
|
+
Requires-Dist: numpy>=1.26; extra == 'numpy'
|
|
48
|
+
Provides-Extra: pandas
|
|
49
|
+
Requires-Dist: pandas>=2.0; extra == 'pandas'
|
|
50
|
+
Provides-Extra: polars
|
|
51
|
+
Requires-Dist: polars>=1.0; extra == 'polars'
|
|
52
|
+
Description-Content-Type: text/markdown
|
|
53
|
+
|
|
54
|
+
<p align="center">
|
|
55
|
+
<picture>
|
|
56
|
+
<source media="(prefers-color-scheme: dark)" srcset="docs/logo-dark.svg">
|
|
57
|
+
<source media="(prefers-color-scheme: light)" srcset="docs/logo.svg">
|
|
58
|
+
<img src="docs/logo.svg" alt="assertpy" width="280">
|
|
59
|
+
</picture>
|
|
60
|
+
<br>
|
|
61
|
+
<b>Fully typed fluent assertion library for Python</b>
|
|
62
|
+
</p>
|
|
63
|
+
|
|
64
|
+
<p align="center">
|
|
65
|
+
<a href="https://github.com/assertpy/assertpy/actions/workflows/ci.yml"><img src="https://img.shields.io/github/actions/workflow/status/assertpy/assertpy/ci.yml?branch=main&label=CI" alt="CI"></a>
|
|
66
|
+
<a href="https://codecov.io/gh/assertpy/assertpy"><img src="https://codecov.io/gh/assertpy/assertpy/graph/badge.svg" alt="Coverage"></a>
|
|
67
|
+
<a href="https://pypi.org/project/assertpy/"><img src="https://img.shields.io/pypi/v/assertpy" alt="PyPI version"></a>
|
|
68
|
+
<a href="https://pypi.org/project/assertpy/"><img src="https://img.shields.io/pypi/pyversions/assertpy" alt="Python"></a>
|
|
69
|
+
<a href="https://pepy.tech/projects/assertpy"><img src="https://static.pepy.tech/badge/assertpy/month" alt="Downloads"></a>
|
|
70
|
+
<br>
|
|
71
|
+
<a href="https://assertpy.github.io/assertpy/concepts/type-safety/"><img src="https://img.shields.io/badge/overloads-ty%20%7C%20mypy%20%7C%20pyright%20%7C%20pyrefly-2ea043" alt="every public overload checked by ty, mypy --strict, pyright and pyrefly with zero suppressions"></a>
|
|
72
|
+
<a href="https://assertpy.github.io/assertpy/"><img src="https://img.shields.io/badge/docs-online-black" alt="Documentation"></a>
|
|
73
|
+
<a href="https://scorecard.dev/viewer/?uri=github.com/assertpy/assertpy"><img src="https://img.shields.io/ossf-scorecard/github.com/assertpy/assertpy?label=OpenSSF%20Scorecard" alt="OpenSSF Scorecard"></a>
|
|
74
|
+
</p>
|
|
75
|
+
|
|
76
|
+
<h2 align="center"><a href="https://assertpy.github.io/assertpy/getting-started/quickstart/">Quick start</a></h2>
|
|
77
|
+
|
|
78
|
+
```bash
|
|
79
|
+
pip install assertpy
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
```python
|
|
83
|
+
from assertpy import assert_that
|
|
84
|
+
|
|
85
|
+
def test_user():
|
|
86
|
+
user = {"name": "Alice", "age": 30, "roles": ["viewer", "editor"]}
|
|
87
|
+
|
|
88
|
+
assert_that(user).contains_key("name", "age")
|
|
89
|
+
assert_that(user).contains_entry({"name": "Alice"})
|
|
90
|
+
assert_that(user["age"]).is_between(18, 120)
|
|
91
|
+
assert_that(user["roles"]).contains("viewer").does_not_contain("admin")
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
The [full documentation](https://assertpy.github.io/assertpy/) covers every assertion, matcher, and integration.
|
|
95
|
+
|
|
96
|
+
<table>
|
|
97
|
+
<tr>
|
|
98
|
+
<td valign="top" width="50%">
|
|
99
|
+
<a href="#why-fluent-assertions"><b>Failures that point at the difference</b></a><br>
|
|
100
|
+
A recursive diff names the exact path that differs, in color, instead of dumping both structures
|
|
101
|
+
</td>
|
|
102
|
+
<td valign="top" width="50%">
|
|
103
|
+
<a href="#type-aware-autocomplete"><b>Type-aware autocomplete</b></a><br>
|
|
104
|
+
<code>assert_that()</code> returns a protocol per value type, so your IDE offers the methods that fit
|
|
105
|
+
</td>
|
|
106
|
+
</tr>
|
|
107
|
+
<tr>
|
|
108
|
+
<td valign="top">
|
|
109
|
+
<a href="#typed-narrowing"><b>Typed narrowing</b></a><br>
|
|
110
|
+
An assertion hands the value back statically narrowed, with no <code>cast</code> and no bare <code>assert</code>
|
|
111
|
+
</td>
|
|
112
|
+
<td valign="top">
|
|
113
|
+
<a href="#features"><b>Composable matchers</b></a><br>
|
|
114
|
+
45 matchers that combine with <code>&</code>, <code>|</code>, <code>~</code> and nest inside the expected structure itself
|
|
115
|
+
</td>
|
|
116
|
+
</tr>
|
|
117
|
+
<tr>
|
|
118
|
+
<td valign="top">
|
|
119
|
+
<a href="#features"><b>Built for test suites</b></a><br>
|
|
120
|
+
Soft assertions, polling for eventual consistency, snapshots, and expected-exception chains
|
|
121
|
+
</td>
|
|
122
|
+
<td valign="top">
|
|
123
|
+
<a href="#integrations"><b>Integrations</b></a><br>
|
|
124
|
+
Allure, Behave, JSON Path and Schema, pandas, polars, numpy, and OpenAPI response contracts
|
|
125
|
+
</td>
|
|
126
|
+
</tr>
|
|
127
|
+
</table>
|
|
128
|
+
|
|
129
|
+
<h2 align="center"><a href="https://assertpy.github.io/assertpy/getting-started/comparison/">Why fluent assertions?</a></h2>
|
|
130
|
+
|
|
131
|
+
`assert` states a condition well. pytest reports it well.
|
|
132
|
+
|
|
133
|
+
What it cannot say is *where* two structures differ. It prints both and leaves the reading to you:
|
|
134
|
+
|
|
135
|
+
```text
|
|
136
|
+
assert response == expected
|
|
137
|
+
E AssertionError: assert {'id': 1, ...} == {'id': 1, ...}
|
|
138
|
+
E Omitting 1 identical items, use -vv to show
|
|
139
|
+
E Differing items:
|
|
140
|
+
E {'user': {'name': 'Alice', 'role': 'superadmin'}} != {'user': {'name': 'Alice', 'role': 'admin'}}
|
|
141
|
+
E {'status': 'active'} != {'status': 'disabled'}
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
assertpy names the [exact path](https://assertpy.github.io/assertpy/guides/errors/#rich-pytest-diffs), in color:
|
|
145
|
+
|
|
146
|
+
<!-- docs-guard: skip -->
|
|
147
|
+
```python
|
|
148
|
+
assert_that(response).is_equal_to(expected)
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
<p align="center">
|
|
152
|
+
<img src="https://raw.githubusercontent.com/assertpy/assertpy/main/docs/assets/diff-equal.png" width="300" alt="Structured diff in the terminal: user.role shown with its path, removal in red and addition in green">
|
|
153
|
+
</p>
|
|
154
|
+
|
|
155
|
+
It recurses through nested containers. Matcher predicates get the same treatment.
|
|
156
|
+
|
|
157
|
+
For dynamic fields like IDs, assert a subset with
|
|
158
|
+
[`matches_structure()`](https://assertpy.github.io/assertpy/guides/matchers/#structural-matching).
|
|
159
|
+
|
|
160
|
+
The chain is the other half. Your IDE offers only the
|
|
161
|
+
[methods that fit the value](https://assertpy.github.io/assertpy/concepts/type-safety/).
|
|
162
|
+
|
|
163
|
+
<!-- docs-guard: skip -->
|
|
164
|
+
```python
|
|
165
|
+
assert_that(items).is_instance_of(list).is_length(3).contains("admin")
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
Matchers are ordinary values that answer `==`, the way `unittest.mock.ANY` does.
|
|
169
|
+
|
|
170
|
+
Nothing is patched, so a matcher can sit inside the expected structure at any depth:
|
|
171
|
+
|
|
172
|
+
```python
|
|
173
|
+
response = {"id": 7, "user": {"name": "Alice", "age": 30}, "tags": ["a", "b"]}
|
|
174
|
+
|
|
175
|
+
assert_that(response).is_equal_to(
|
|
176
|
+
{
|
|
177
|
+
"id": match.greater_than(0),
|
|
178
|
+
"user": {"name": "Alice", "age": match.between(18, 120)},
|
|
179
|
+
"tags": ["a", "b"],
|
|
180
|
+
}
|
|
181
|
+
)
|
|
182
|
+
|
|
183
|
+
# or keep the bare `assert`, and pytest's own rewriting reports it
|
|
184
|
+
assert response == {
|
|
185
|
+
"id": match.greater_than(0),
|
|
186
|
+
"user": match.ignore(),
|
|
187
|
+
"tags": ["a", "b"],
|
|
188
|
+
}
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
The fluent form keeps the path-level diff, the bare form keeps pytest's.
|
|
192
|
+
|
|
193
|
+
There are [45 matchers](https://assertpy.github.io/assertpy/guides/matchers/), combining with `&`, `|` and `~`.
|
|
194
|
+
|
|
195
|
+
<p align="center">
|
|
196
|
+
<img src="https://raw.githubusercontent.com/assertpy/assertpy/main/docs/assets/diff-gallery.png" width="640" alt="Structured diffs in the terminal: dict path, list element, set extra/missing, and structural-matcher predicate diffs, side by side">
|
|
197
|
+
</p>
|
|
198
|
+
|
|
199
|
+
<h2 align="center"><a href="https://assertpy.github.io/assertpy/concepts/type-safety/">Type-aware autocomplete</a></h2>
|
|
200
|
+
|
|
201
|
+
`assert_that()` uses `@overload` to return type-specific Protocols.<br>
|
|
202
|
+
Your IDE shows only methods relevant to the value you're testing, not the whole surface:
|
|
203
|
+
|
|
204
|
+
- `assert_that("hello").` → string methods: `starts_with`, `matches`, `is_alpha`, ...
|
|
205
|
+
- `assert_that(42).` → numeric methods: `is_positive`, `is_between`, `is_close_to`, ...
|
|
206
|
+
- `assert_that(Path("/tmp")).` → path methods: `exists`, `is_file`, `is_readable`, ...
|
|
207
|
+
- `assert_that(my_dict).` → dict methods: `contains_key`, `contains_entry`, `has_json_path`, ...
|
|
208
|
+
- `assert_that(b"\x89PNG").` → bytes methods: `starts_with_bytes`, `is_valid_utf8`, `decoded_as`, ...
|
|
209
|
+
|
|
210
|
+
15 type-specific Protocols instead of one `Any`<br>
|
|
211
|
+
Works in PyCharm, VS Code, and any editor that runs a type checker
|
|
212
|
+
|
|
213
|
+
<h2 align="center"><a href="https://assertpy.github.io/assertpy/concepts/type-safety/#typed-narrowing-with-value">Typed narrowing</a></h2>
|
|
214
|
+
|
|
215
|
+
An assertion hands the value back, statically narrowed.
|
|
216
|
+
|
|
217
|
+
`is_not_none()` strips `None`, `is_instance_of()` narrows to the class, and `.value` returns it:
|
|
218
|
+
|
|
219
|
+
<!-- docs-guard: skip -->
|
|
220
|
+
```python
|
|
221
|
+
order = assert_that(repo.find(42)).is_not_none().is_instance_of(PaidOrder).value
|
|
222
|
+
order.refund() # statically PaidOrder - verified by ty, mypy, pyright and pyrefly
|
|
223
|
+
```
|
|
224
|
+
|
|
225
|
+
For API tests,
|
|
226
|
+
[`assert_conforms()`](https://assertpy.github.io/assertpy/concepts/type-safety/#contract-narrowing-with-assert_conforms)
|
|
227
|
+
validates a payload against a Pydantic model and narrows to it. `exact=True` catches contract drift:
|
|
228
|
+
|
|
229
|
+
<!-- docs-guard: skip -->
|
|
230
|
+
```python
|
|
231
|
+
data = assert_conforms(response.json(), OrderModel).value # data: OrderModel
|
|
232
|
+
```
|
|
233
|
+
|
|
234
|
+
<h2 align="center"><a href="https://assertpy.github.io/assertpy/guides/errors/#asking-instead-of-asserting">A failure you can read from code</a></h2>
|
|
235
|
+
|
|
236
|
+
An exception is the right default. Reading the result means catching it.
|
|
237
|
+
|
|
238
|
+
`check()` runs the next assertion for its verdict instead:
|
|
239
|
+
|
|
240
|
+
```python
|
|
241
|
+
response = {"user": {"name": "Alice", "role": "superadmin"}, "status": "active"}
|
|
242
|
+
expected = {"user": {"name": "Alice", "role": "admin"}, "status": "active"}
|
|
243
|
+
|
|
244
|
+
outcome = assert_that(response).check().is_equal_to(expected)
|
|
245
|
+
|
|
246
|
+
if not outcome and outcome.diff:
|
|
247
|
+
print(outcome.diff.entries[0].path) # user.role
|
|
248
|
+
```
|
|
249
|
+
|
|
250
|
+
It is truthy when the assertion held. When it did not, it carries `.message`, `.actual`, `.expected`
|
|
251
|
+
and a walkable `.diff`. So does `AssertionFailure`.
|
|
252
|
+
|
|
253
|
+
So a reporter reads structure instead of parsing a string. That is how the
|
|
254
|
+
[Allure integration](https://assertpy.github.io/assertpy/extending/integrations/#allure) works.
|
|
255
|
+
Anything else you build can too.
|
|
256
|
+
|
|
257
|
+
<h2 align="center">Features</h2>
|
|
258
|
+
|
|
259
|
+
**Fluent API**
|
|
260
|
+
|
|
261
|
+
- [**Structural matching**](https://assertpy.github.io/assertpy/guides/matchers/#structural-matching): `matches_structure()` for declarative dict/API-response validation
|
|
262
|
+
- [**Recursive field assertions**](https://assertpy.github.io/assertpy/guides/assertions/#recursive-field-assertions): `all_fields_satisfy()` / `has_no_none_fields()` apply a predicate to every leaf of an object graph
|
|
263
|
+
- [**Universal negation**](https://assertpy.github.io/assertpy/guides/fluent/#universal-negation): `.not_` inverts any assertion, no dedicated `is_not_*` methods
|
|
264
|
+
- [**Collection pipeline**](https://assertpy.github.io/assertpy/guides/fluent/#collection-pipeline): `filtered_on()`, `mapped()`, `flat_mapped()`, `first()`, `last()`, `element()`, `single()`
|
|
265
|
+
- [**Positional & pairwise checks**](https://assertpy.github.io/assertpy/guides/assertions/#lists): `satisfies_exactly()`, `zip_satisfies()`, `contains_only_once()`, `has_same_size_as()`, plus `*_in_any_order` variants
|
|
266
|
+
|
|
267
|
+
**Type safety**
|
|
268
|
+
|
|
269
|
+
- [**Refinement predicates**](https://assertpy.github.io/assertpy/concepts/type-safety/#refinement-narrowing-with-a-typeis-predicate-advanced): `satisfies()` takes a `TypeIs` predicate, so a domain check narrows the chain too
|
|
270
|
+
- [**Contract testing**](https://assertpy.github.io/assertpy/concepts/type-safety/#contract-narrowing-with-assert_conforms): `assert_conforms()` validates a raw payload against a Pydantic model and narrows to it. [`exact=True`](https://assertpy.github.io/assertpy/concepts/type-safety/#contract-drift-with-exacttrue) catches contract drift, `each=True` validates list endpoints
|
|
271
|
+
|
|
272
|
+
**Built-in types**
|
|
273
|
+
|
|
274
|
+
- [Strings](https://assertpy.github.io/assertpy/guides/assertions/#strings), [numbers](https://assertpy.github.io/assertpy/guides/assertions/#numbers), [lists](https://assertpy.github.io/assertpy/guides/assertions/#lists), [tuples](https://assertpy.github.io/assertpy/guides/assertions/#tuples), [sets](https://assertpy.github.io/assertpy/guides/assertions/#sets), [dicts](https://assertpy.github.io/assertpy/guides/assertions/#dicts), [dates](https://assertpy.github.io/assertpy/guides/assertions/#dates), [booleans](https://assertpy.github.io/assertpy/guides/assertions/#booleans), [objects](https://assertpy.github.io/assertpy/guides/assertions/#objects), [bytes](https://assertpy.github.io/assertpy/guides/assertions/#bytes--bytearray), [files](https://assertpy.github.io/assertpy/guides/assertions/#files), [exceptions](https://assertpy.github.io/assertpy/guides/errors/#expected-exceptions)
|
|
275
|
+
- [**Bytes assertions**](https://assertpy.github.io/assertpy/guides/assertions/#bytes--bytearray): `is_valid_utf8()`, `starts_with_bytes()`, `is_hex_equal_to()`, `decoded_as()` for `bytes`/`bytearray`
|
|
276
|
+
- [**Dynamic assertions**](https://assertpy.github.io/assertpy/guides/assertions/#dynamic-assertions-on-objects): `has_<name>()` for any attribute, property, or zero-argument method
|
|
277
|
+
- [**Dict comparison**](https://assertpy.github.io/assertpy/guides/assertions/#selective-comparison-ignore--include): `is_equal_to(ignore=..., include=...)` for selective key/field matching by name, regex, or type
|
|
278
|
+
- [**Recursive comparison**](https://assertpy.github.io/assertpy/guides/assertions/#recursive-comparison-tolerance--custom-comparators): `is_equal_to()` with `tolerance`, `comparators`, or `ignore_null` for nested structures
|
|
279
|
+
- [**Extracting**](https://assertpy.github.io/assertpy/guides/assertions/#extracting-attributes-from-objects): flatten collections on attributes with `filter` and `sort` support
|
|
280
|
+
- [**Regex group extraction**](https://assertpy.github.io/assertpy/guides/data/#regex-group-extraction): `extracting_group()` and `matches_with_groups()` for regex captures
|
|
281
|
+
|
|
282
|
+
**Testing**
|
|
283
|
+
|
|
284
|
+
- [**Soft assertions**](https://assertpy.github.io/assertpy/guides/testing/#soft-assertions): thread-safe and async-safe via `contextvars`, each failure reported with its `file:line`. Group with `sa.group()` or `assert_all()`
|
|
285
|
+
- [**Polling assertions**](https://assertpy.github.io/assertpy/guides/testing/#async-assertions): `eventually()` (async) / `eventually_sync()` (blocking) retry for eventual consistency, with a convergence trace on timeout
|
|
286
|
+
- [**Expected exceptions**](https://assertpy.github.io/assertpy/guides/errors/#expected-exceptions): `raises().when_called_with()`, walk the cause chain (`caused_by()`, `has_root_cause()`), search an `ExceptionGroup` (`contains_error()`, `errors()`, `error_of()`), or pivot to the object (`raised()`)
|
|
287
|
+
- [**HTTP responses**](https://assertpy.github.io/assertpy/recipes/#test-an-http-api-response): assert on the response itself and every failure names the request it came from, with `decoded_as_json()` to step into the body. No client library is a dependency
|
|
288
|
+
- [**Snapshot testing**](https://assertpy.github.io/assertpy/guides/testing/#snapshot-testing): an external JSON file, an [inline](https://assertpy.github.io/assertpy/guides/testing/#inline-snapshots) value recorded into the test source, or a [value-tolerant contract](https://assertpy.github.io/assertpy/guides/testing/#contract-snapshots), all updated with `--assertpy-snapshot-update`
|
|
289
|
+
- [**OpenAPI response contracts**](https://assertpy.github.io/assertpy/reference/json/#assertpy.json_mixin.JsonMixin.conforms_to_openapi): `conforms_to_openapi()` checks a JSON body against an operation's response schema, reporting every violation with its JSON path
|
|
290
|
+
|
|
291
|
+
**Failure reporting**
|
|
292
|
+
|
|
293
|
+
- [**Structured errors**](https://assertpy.github.io/assertpy/guides/errors/#structured-errors): `AssertionFailure` carries `.actual`, `.expected` and `.diff`, and the diff renders into the message, so it shows off pytest too
|
|
294
|
+
- [**Assertions as values**](https://assertpy.github.io/assertpy/guides/errors/#asking-instead-of-asserting): `check()` runs the next assertion for its verdict instead of raising, handing back an `AssertionOutcome`
|
|
295
|
+
- [**Rich pytest diffs**](https://assertpy.github.io/assertpy/guides/errors/#rich-pytest-diffs): recursive diffs across containers, dataclasses, attrs and Pydantic models, with intra-line carets for strings
|
|
296
|
+
- [**Failures that say why**](https://assertpy.github.io/assertpy/guides/errors/#some-failures-say-why-not-only-what): where one fact explains a failure, a line under the message says it. An id the payload holds as text (`7` against `"7"`), a NaN, two instances compared by identity, surrounding whitespace. A failed `contains()` or `is_in()` names the closest row, and forty rows that differ in one field are named as one place
|
|
297
|
+
|
|
298
|
+
**Plugin for pytest**
|
|
299
|
+
|
|
300
|
+
Set from the command line or from `[tool.pytest.ini_options]`, not from a call
|
|
301
|
+
|
|
302
|
+
- [**Failure clustering**](https://assertpy.github.io/assertpy/guides/errors/#what-the-failures-had-in-common): forty failing tests are usually not forty problems. Where three or more differ at the same place, the run ends with a line saying where. On by default, `assertpy_failure_clusters = "off"` turns it off
|
|
303
|
+
- [**IDE comparison window**](https://assertpy.github.io/assertpy/guides/errors/#the-comparison-window-of-an-ide): a failed `is_equal_to()` or `has_<name>()` gets PyCharm's "Click to see difference" link, as a plain `assert` does, and the window shows the two values it compared. Under `ignore=` or `include=` those go without the keys left out. The plugin hands them to `pytest_assertrepr_compare`, the hook the IDE's runner listens to. On by default, `assertpy_comparison = "off"` turns it off
|
|
304
|
+
- [**Diagnostic profiles**](https://assertpy.github.io/assertpy/guides/errors/#configuration): `assertpy_profile` turns the guards below on in one line. `compatible` (default) leaves them off, `safe` warns, `strict` fails the tests they find. A setting you name yourself still wins
|
|
305
|
+
- [**Vacuous-assertion guard**](https://assertpy.github.io/assertpy/guides/assertions/#assertions-that-checked-nothing): `--assertpy-vacuous` warns when a universal assertion passes over an empty collection, having checked nothing
|
|
306
|
+
- [**Dangling-assertion detector**](https://assertpy.github.io/assertpy/guides/assertions/#assertions-that-never-ran): `--assertpy-dangling` warns when a chain builds an assertion and never runs it. `assert assert_that(x).is_positive` passes on any value, and neither ruff nor coverage sees it
|
|
307
|
+
|
|
308
|
+
**Extensibility**
|
|
309
|
+
|
|
310
|
+
- [**Custom matchers**](https://assertpy.github.io/assertpy/guides/matchers/#custom-matchers): `register_matcher()` composes existing ones, `BaseMatcher` carries its own predicate. Both compose with `&`, `|`, `~`
|
|
311
|
+
- [**Custom assertions**](https://assertpy.github.io/assertpy/extending/custom-assertions/): `add_extension()` adds a method to the builder
|
|
312
|
+
|
|
313
|
+
<h2 align="center"><a href="https://assertpy.github.io/assertpy/extending/integrations/">Integrations</a></h2>
|
|
314
|
+
|
|
315
|
+
- [**Allure**](https://assertpy.github.io/assertpy/extending/integrations/#allure) (`pip install assertpy[allure]`): the pytest plugin auto-attaches structured diff and actual/expected data to Allure reports, in three configurable modes
|
|
316
|
+
- [**Behave**](https://assertpy.github.io/assertpy/extending/integrations/#behave) (`pip install assertpy[behave]`): ready-made parameter types (`PositiveInt`, `NonEmptyString`, ...) for step definitions like `{age:PositiveInt}`
|
|
317
|
+
- [**JSON**](https://assertpy.github.io/assertpy/guides/data/#json-path--schema) (`pip install assertpy[json]`): JSONPath navigation (`at_json_path()`, `has_json_path()`) and JSON Schema validation (`matches_json_schema()`)
|
|
318
|
+
- [**Data frames**](https://assertpy.github.io/assertpy/extending/integrations/#data-frames-and-arrays) (`pip install assertpy[pandas]` / `[polars]` / `[numpy]`): fluent equality for pandas/polars frames and numpy arrays, carrying each library's own diff
|
|
319
|
+
|
|
320
|
+
---
|
|
321
|
+
|
|
322
|
+
<p align="center">
|
|
323
|
+
<a href="https://github.com/assertpy/assertpy/blob/main/LICENSE">BSD 3-Clause License</a>
|
|
324
|
+
</p>
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
assertpy/__init__.py,sha256=Rf_S0hzUuzp1n6oRAqoHhCSLS6TzJ8YcB7TmU-74mmg,1835
|
|
2
|
+
assertpy/_clustering.py,sha256=VJRziYs35uNSRSW3jCw3aoMws5YQiysCzpshg9k7GX0,25569
|
|
3
|
+
assertpy/_dangling.py,sha256=0tERUBRQJhhPWF55WAFgkM7sz78T4ncOUQYcrGZSdxw,24916
|
|
4
|
+
assertpy/_hints.py,sha256=Xofscs0ZXzrrJMqThn_xqFqC5V3VrRy9i_KbN556zNo,59046
|
|
5
|
+
assertpy/_inline.py,sha256=QhNY-O4wVko1tqood9XlC82I-_HRa8Ld4dTOkpOdaog,21951
|
|
6
|
+
assertpy/_matcher_impls.py,sha256=GtUcIcOFxqfZkz75kRRYn-EQNpceqiFdAjFg2zoMldA,81000
|
|
7
|
+
assertpy/_passes.py,sha256=lKILSfdxO3b4vWLq4KMwd1MBUUMoWFCnsBKLVq_brP8,10283
|
|
8
|
+
assertpy/_renamed.py,sha256=1n2UEJ70BFqQbvaIu6A6KOiMF2A7f3BhrxmPBbQipoE,1849
|
|
9
|
+
assertpy/_satisfies.py,sha256=sm25E4PjxAAEbaCn9TlIDqSE4NorxVFYGk36wdUm4P8,36055
|
|
10
|
+
assertpy/_snapshot_codec.py,sha256=L3uJnWFB3m20IT4UAyLOScStq0y873hSSdakIBKPOfs,13247
|
|
11
|
+
assertpy/assertpy.py,sha256=ETv6uDjBz_q8nQ3hHCoskAHNo96U81ruqvsJpVsctpw,121672
|
|
12
|
+
assertpy/async_assertions.py,sha256=HIg7PbZ3lqovBQAfaBnwmT_KxDwd4ofSJyIz_AdVFLM,41947
|
|
13
|
+
assertpy/base.py,sha256=Hr9_WEXzNPUr5202i7W3FZ-p7iUb9NcVWYlUeZ7RYLw,39155
|
|
14
|
+
assertpy/behave_matchers.py,sha256=46bFFVa31oXK9edvcCqk4JUaAeVU1DGWhV9l8fIDCic,2865
|
|
15
|
+
assertpy/bytes_mixin.py,sha256=hoin-zxlMN31L0kpWYLwUWwMnO77tacTQy8HM_ppQJo,5972
|
|
16
|
+
assertpy/collection.py,sha256=-fBHGBCCxVdBEQ0GltNqQjH349pF9Odu2u3GiR9ghsc,20683
|
|
17
|
+
assertpy/contains.py,sha256=H62NmHmOVT12Cv8UkbmQ9jtDU4JyIIPh73MAPlgJB5E,47433
|
|
18
|
+
assertpy/dataframe.py,sha256=e4LC8_rhlGcBBBcdrRMPpgT-lhn_e6sCssjM3wFvy3s,9647
|
|
19
|
+
assertpy/date.py,sha256=FEE7d2EFw518CIdYy6V3yppamSL_-EiLkMMrGcAjwGc,13102
|
|
20
|
+
assertpy/dict.py,sha256=CaXDMD72hPTJm2ClrmXWRsVD4iscqcFx8v826cqOQII,10006
|
|
21
|
+
assertpy/dynamic.py,sha256=kjFMJgklJ4vzNStvj3AqpWrDnO_H9ULvGFB0XyBMiqg,11705
|
|
22
|
+
assertpy/errors.py,sha256=7ZJD4C251iP-rrdXa-QbAiggbP3bCahshcPNhi8_Qdc,37119
|
|
23
|
+
assertpy/exception.py,sha256=QbFuOGRC2bfdCr6Oi1f02HjxdRh_Y1z4eT1MaMJp80s,38916
|
|
24
|
+
assertpy/extracting.py,sha256=3YOdyngjSjwTTJ6KemM1xnucp1aXHjU7w02ulUDToTY,12173
|
|
25
|
+
assertpy/file.py,sha256=QE0bEYsJNmzEmGxVqsFzY639QwcWL3XsiYYMl6Ecmpw,11869
|
|
26
|
+
assertpy/helpers.py,sha256=h5VfNtyPBCasO0Ouqiq9AnOIFrhSYaogBE5NcuXTLPg,35609
|
|
27
|
+
assertpy/http_mixin.py,sha256=IyEEc8S833rTqqTZkeAaLM_Fj-mTg7buV7FflRwsCMU,10363
|
|
28
|
+
assertpy/json_mixin.py,sha256=m1hebcT3Yi6onDFcHmDusebc_LAtEyrtx04PZ9a6taM,59463
|
|
29
|
+
assertpy/matchers.py,sha256=pB4PkVnOzUSVyUXoM9cVIKWm8sxPuStFffPl3GLQk7U,25364
|
|
30
|
+
assertpy/numeric.py,sha256=b5ZgxH_EiVupEYr5gVL0Xn7z1XJNxR4djUNKMU_BGgY,26434
|
|
31
|
+
assertpy/outcome.py,sha256=TcB5POBCobIH7aaoP8m5O-vW_qelpN6og-pl7dCX7oU,7152
|
|
32
|
+
assertpy/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
33
|
+
assertpy/pytest_plugin.py,sha256=G8qwCLX2Pdp62NbH_8n9Bqmg7bz9cjcZx5rZiND-hkc,74962
|
|
34
|
+
assertpy/snapshot.py,sha256=Kr6K-vRBbzhuVARGqCIcIPP5IvPdwSazF42CqJw-ZSQ,49850
|
|
35
|
+
assertpy/string.py,sha256=qrfoiYvx_L4cAyVOCVfAvz8v99Vd7dfwq4XNY0K6b1k,32804
|
|
36
|
+
assertpy/warning.py,sha256=htSduXVFuFPHs5eRmM8O9gq2oUowVDAgLQooQ0NMr9Y,6762
|
|
37
|
+
assertpy/_engine/__init__.py,sha256=xvRm6rIBsDmUvMEVWciNeVHw3I_AiVCUUyya_75nIlw,91
|
|
38
|
+
assertpy/_engine/_builder_check_typing.py,sha256=CxS-ojY2PYnshBhuuvnuSl0yyvrYGXnR3KDwmRdDWGM,55705
|
|
39
|
+
assertpy/_engine/_capable_typing.py,sha256=NSYZgncn-Xal-5lsOnFftbmAA_LZDom0l8P8lu-p1FA,43304
|
|
40
|
+
assertpy/_engine/_check_typing.py,sha256=x6GHpvMGb7CQKdOdwFxWqEheS6qZUOqYYfwxD6MkQ8c,38146
|
|
41
|
+
assertpy/_engine/_compare.py,sha256=4zsxtujLT0R3MGwTJjRUrkR1xnHiHpBTnH7Y70Ky1rg,49026
|
|
42
|
+
assertpy/_engine/_compat.py,sha256=P2W8_P7c4WNiAF4P0f_t7KmpJ85fk6-z_j_IKpF57xQ,1742
|
|
43
|
+
assertpy/_engine/_contract.py,sha256=YmhmZoIT53AZeyOB9xoQGDFumXPOpMaXn8g-0uDnuI0,91115
|
|
44
|
+
assertpy/_engine/_diff.py,sha256=Rj3SGm-AW2FjJMP_rn1AhWqQla_2U78Kctu8L1Hp3-k,55669
|
|
45
|
+
assertpy/_engine/_equality.py,sha256=W_LEf2K6lmRXiwV45yTOhBqbG_FW6ZOQbKipuCoOKFc,40647
|
|
46
|
+
assertpy/_engine/_introspection.py,sha256=A2S_FvoynFGZGYyKnF1yM44pTrbIYx7bEAqYgbv6OHo,33572
|
|
47
|
+
assertpy/_engine/_membership.py,sha256=cq2Roes8tq1kXH9VtyK4Wr4GZ51ZWPgpF3ZwOB4NoRY,24267
|
|
48
|
+
assertpy/_engine/_mixin_base.py,sha256=mSyIybzvnbhO02a6BRsAvRfVsTI-o3wjqkBDQRwZaSE,10296
|
|
49
|
+
assertpy/_engine/_negated_typing.py,sha256=UpveYN_KBj8va_Yd51SbrmiygrkjxXkmiq8YXHzuTrM,123905
|
|
50
|
+
assertpy/_engine/_operations.py,sha256=TncowBzkbVGhl5UK-jBDNewPzm4dywguzwyOo_EweAs,3037
|
|
51
|
+
assertpy/_engine/_ordering.py,sha256=wOKyH-MO2sGDGa5x66_BBzm3a5OGBwljF9Wmo6FCDuA,44498
|
|
52
|
+
assertpy/_engine/_pairing.py,sha256=4YuylWUS3uCBOXzMu9Rd_VIzGItdRqhRLjw1RTdWoKo,2700
|
|
53
|
+
assertpy/_engine/_path.py,sha256=rKggawa5iiOxaXl4jITlbqbeZZ7sIVYocWe8w2BCTOg,7565
|
|
54
|
+
assertpy/_engine/_poll_typing.py,sha256=KiXF-PFTM_xP7Mea8PW5pvXmQNJEmRORUHPBJPbfNXI,253721
|
|
55
|
+
assertpy/_engine/_require.py,sha256=SiNmreJZ9tjDfGEyaJiF9zMIIRQlZqQkLzoMRGoPX4M,14897
|
|
56
|
+
assertpy/_engine/_size.py,sha256=D1ezRB7LA6UsuoyIQvwHbt0wStDefFlx4vukRxe46do,1519
|
|
57
|
+
assertpy/_engine/_text.py,sha256=HMhlBLHGRO5mClvtXHVAEbwjYpRiNGKGRppPl69o3A4,1737
|
|
58
|
+
assertpy/_engine/_typing.py,sha256=ZGc6qbCY6BIeHcrBzzq-CPDE5ZwfYnFi5ZFGDEID-PM,63766
|
|
59
|
+
assertpy-2.35.0.dist-info/METADATA,sha256=G40ygiMryOkSTpCr55gal9DQdLGx3MazsNcKppIEu2U,21103
|
|
60
|
+
assertpy-2.35.0.dist-info/WHEEL,sha256=2iZrJkDIkeDh0k9SikKMqFuRP-ua6mb4Ir1AsER4BEw,87
|
|
61
|
+
assertpy-2.35.0.dist-info/entry_points.txt,sha256=0bXpJmVXTIHw3qgPiN62hfbYztMJidYdYsPUvkJZq74,45
|
|
62
|
+
assertpy-2.35.0.dist-info/licenses/LICENSE,sha256=ep-e6x7HTPIdDUdZXDzKJIpOUZJg5Y1Q6kI56qYjPbM,1547
|
|
63
|
+
assertpy-2.35.0.dist-info/RECORD,,
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
Copyright (c) 2015-2019, Activision Publishing, Inc.
|
|
2
|
+
Copyright (c) 2026, Solganis
|
|
3
|
+
All rights reserved.
|
|
4
|
+
|
|
5
|
+
Redistribution and use in source and binary forms, with or without modification,
|
|
6
|
+
are permitted provided that the following conditions are met:
|
|
7
|
+
|
|
8
|
+
1. Redistributions of source code must retain the above copyright notice, this
|
|
9
|
+
list of conditions and the following disclaimer.
|
|
10
|
+
|
|
11
|
+
2. Redistributions in binary form must reproduce the above copyright notice,
|
|
12
|
+
this list of conditions and the following disclaimer in the documentation
|
|
13
|
+
and/or other materials provided with the distribution.
|
|
14
|
+
|
|
15
|
+
3. Neither the name of the copyright holder nor the names of its contributors
|
|
16
|
+
may be used to endorse or promote products derived from this software without
|
|
17
|
+
specific prior written permission.
|
|
18
|
+
|
|
19
|
+
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
|
20
|
+
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
|
21
|
+
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
|
22
|
+
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR
|
|
23
|
+
ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
|
24
|
+
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
|
25
|
+
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
|
|
26
|
+
ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
|
27
|
+
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
|
28
|
+
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|