assertpy2 2.10.0__tar.gz → 2.12.0__tar.gz

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 (155) hide show
  1. {assertpy2-2.10.0 → assertpy2-2.12.0}/.github/workflows/mutation.yml +2 -2
  2. {assertpy2-2.10.0 → assertpy2-2.12.0}/PKG-INFO +34 -67
  3. {assertpy2-2.10.0 → assertpy2-2.12.0}/README.md +23 -66
  4. assertpy2-2.12.0/assertpy2/_compare.py +136 -0
  5. {assertpy2-2.10.0 → assertpy2-2.12.0}/assertpy2/_compat.py +1 -1
  6. {assertpy2-2.10.0 → assertpy2-2.12.0}/assertpy2/_mixin_base.py +16 -3
  7. {assertpy2-2.10.0 → assertpy2-2.12.0}/assertpy2/_typing.py +22 -4
  8. {assertpy2-2.10.0 → assertpy2-2.12.0}/assertpy2/assertpy.py +49 -46
  9. {assertpy2-2.10.0 → assertpy2-2.12.0}/assertpy2/async_assertions.py +1 -1
  10. {assertpy2-2.10.0 → assertpy2-2.12.0}/assertpy2/base.py +484 -130
  11. {assertpy2-2.10.0 → assertpy2-2.12.0}/assertpy2/collection.py +44 -13
  12. {assertpy2-2.10.0 → assertpy2-2.12.0}/assertpy2/contains.py +77 -25
  13. assertpy2-2.12.0/assertpy2/dataframe.py +154 -0
  14. {assertpy2-2.10.0 → assertpy2-2.12.0}/assertpy2/date.py +23 -19
  15. {assertpy2-2.10.0 → assertpy2-2.12.0}/assertpy2/dict.py +16 -16
  16. {assertpy2-2.10.0 → assertpy2-2.12.0}/assertpy2/dynamic.py +3 -3
  17. {assertpy2-2.10.0 → assertpy2-2.12.0}/assertpy2/exception.py +21 -16
  18. {assertpy2-2.10.0 → assertpy2-2.12.0}/assertpy2/extracting.py +18 -18
  19. {assertpy2-2.10.0 → assertpy2-2.12.0}/assertpy2/file.py +16 -16
  20. {assertpy2-2.10.0 → assertpy2-2.12.0}/assertpy2/helpers.py +107 -131
  21. {assertpy2-2.10.0 → assertpy2-2.12.0}/assertpy2/json_mixin.py +6 -6
  22. {assertpy2-2.10.0 → assertpy2-2.12.0}/assertpy2/matchers.py +34 -7
  23. {assertpy2-2.10.0 → assertpy2-2.12.0}/assertpy2/numeric.py +40 -39
  24. {assertpy2-2.10.0 → assertpy2-2.12.0}/assertpy2/snapshot.py +38 -38
  25. {assertpy2-2.10.0 → assertpy2-2.12.0}/assertpy2/string.py +31 -30
  26. {assertpy2-2.10.0 → assertpy2-2.12.0}/assertpy2/warning.py +18 -16
  27. {assertpy2-2.10.0 → assertpy2-2.12.0}/docs/assertions.md +64 -0
  28. {assertpy2-2.10.0 → assertpy2-2.12.0}/docs/comparison.md +10 -5
  29. {assertpy2-2.10.0 → assertpy2-2.12.0}/docs/errors.md +3 -3
  30. {assertpy2-2.10.0 → assertpy2-2.12.0}/docs/extending.md +3 -0
  31. {assertpy2-2.10.0 → assertpy2-2.12.0}/docs/fluent.md +7 -1
  32. {assertpy2-2.10.0 → assertpy2-2.12.0}/docs/getting-started.md +2 -1
  33. assertpy2-2.12.0/docs/index.md +103 -0
  34. {assertpy2-2.10.0 → assertpy2-2.12.0}/docs/integrations.md +65 -1
  35. {assertpy2-2.10.0 → assertpy2-2.12.0}/docs/migration.md +1 -1
  36. assertpy2-2.12.0/docs/reference/async.md +13 -0
  37. assertpy2-2.12.0/docs/reference/bytes.md +5 -0
  38. assertpy2-2.12.0/docs/reference/collections.md +5 -0
  39. assertpy2-2.12.0/docs/reference/containment.md +5 -0
  40. assertpy2-2.12.0/docs/reference/core.md +6 -0
  41. assertpy2-2.12.0/docs/reference/dataframes.md +5 -0
  42. assertpy2-2.12.0/docs/reference/dates.md +5 -0
  43. assertpy2-2.12.0/docs/reference/dicts.md +5 -0
  44. assertpy2-2.12.0/docs/reference/dynamic.md +6 -0
  45. assertpy2-2.12.0/docs/reference/entry-points.md +47 -0
  46. assertpy2-2.12.0/docs/reference/errors.md +23 -0
  47. assertpy2-2.12.0/docs/reference/exceptions.md +5 -0
  48. assertpy2-2.12.0/docs/reference/extracting.md +5 -0
  49. assertpy2-2.12.0/docs/reference/files.md +11 -0
  50. assertpy2-2.12.0/docs/reference/json.md +5 -0
  51. assertpy2-2.12.0/docs/reference/matchers.md +7 -0
  52. assertpy2-2.12.0/docs/reference/numbers.md +5 -0
  53. assertpy2-2.12.0/docs/reference/overview.md +21 -0
  54. assertpy2-2.12.0/docs/reference/snapshots.md +5 -0
  55. assertpy2-2.12.0/docs/reference/strings.md +6 -0
  56. assertpy2-2.12.0/docs/reference/warnings.md +5 -0
  57. assertpy2-2.12.0/docs/stylesheets/extra.css +13 -0
  58. assertpy2-2.12.0/mkdocs.yml +141 -0
  59. {assertpy2-2.10.0 → assertpy2-2.12.0}/pyproject.toml +13 -5
  60. {assertpy2-2.10.0 → assertpy2-2.12.0}/tests/test_bytes.py +20 -0
  61. {assertpy2-2.10.0 → assertpy2-2.12.0}/tests/test_chaining.py +3 -3
  62. assertpy2-2.12.0/tests/test_dataframe.py +176 -0
  63. {assertpy2-2.10.0 → assertpy2-2.12.0}/tests/test_datetime.py +120 -120
  64. {assertpy2-2.10.0 → assertpy2-2.12.0}/tests/test_dict_compare.py +18 -18
  65. {assertpy2-2.10.0 → assertpy2-2.12.0}/tests/test_dynamic.py +17 -0
  66. assertpy2-2.12.0/tests/test_equals.py +138 -0
  67. {assertpy2-2.10.0 → assertpy2-2.12.0}/tests/test_extracting.py +20 -0
  68. assertpy2-2.12.0/tests/test_iterable_cluster.py +201 -0
  69. {assertpy2-2.10.0 → assertpy2-2.12.0}/tests/test_matchers.py +3 -3
  70. {assertpy2-2.10.0 → assertpy2-2.12.0}/tests/test_mutation_hardening.py +32 -0
  71. assertpy2-2.12.0/tests/test_recursive_assertion.py +194 -0
  72. assertpy2-2.12.0/tests/test_recursive_compare_config.py +274 -0
  73. {assertpy2-2.10.0 → assertpy2-2.12.0}/tests/test_rich_diff.py +121 -0
  74. {assertpy2-2.10.0 → assertpy2-2.12.0}/tests/test_snapshots.py +1 -1
  75. {assertpy2-2.10.0 → assertpy2-2.12.0}/tests/test_typing.py +18 -0
  76. {assertpy2-2.10.0 → assertpy2-2.12.0}/uv.lock +623 -125
  77. assertpy2-2.10.0/docs/index.md +0 -55
  78. assertpy2-2.10.0/mkdocs.yml +0 -82
  79. assertpy2-2.10.0/tests/test_equals.py +0 -66
  80. {assertpy2-2.10.0 → assertpy2-2.12.0}/.codecov.yml +0 -0
  81. {assertpy2-2.10.0 → assertpy2-2.12.0}/.gitattributes +0 -0
  82. {assertpy2-2.10.0 → assertpy2-2.12.0}/.github/dependabot.yml +0 -0
  83. {assertpy2-2.10.0 → assertpy2-2.12.0}/.github/workflows/ci.yml +0 -0
  84. {assertpy2-2.10.0 → assertpy2-2.12.0}/.github/workflows/codeql.yml +0 -0
  85. {assertpy2-2.10.0 → assertpy2-2.12.0}/.github/workflows/docs.yml +0 -0
  86. {assertpy2-2.10.0 → assertpy2-2.12.0}/.github/workflows/publish.yml +0 -0
  87. {assertpy2-2.10.0 → assertpy2-2.12.0}/.github/workflows/scorecard.yml +0 -0
  88. {assertpy2-2.10.0 → assertpy2-2.12.0}/.github/workflows/zizmor.yml +0 -0
  89. {assertpy2-2.10.0 → assertpy2-2.12.0}/.gitignore +0 -0
  90. {assertpy2-2.10.0 → assertpy2-2.12.0}/CONTRIBUTING.md +0 -0
  91. {assertpy2-2.10.0 → assertpy2-2.12.0}/LICENSE +0 -0
  92. {assertpy2-2.10.0 → assertpy2-2.12.0}/SECURITY.md +0 -0
  93. {assertpy2-2.10.0 → assertpy2-2.12.0}/assertpy2/__init__.py +0 -0
  94. {assertpy2-2.10.0 → assertpy2-2.12.0}/assertpy2/_introspection.py +0 -0
  95. {assertpy2-2.10.0 → assertpy2-2.12.0}/assertpy2/behave_matchers.py +0 -0
  96. {assertpy2-2.10.0 → assertpy2-2.12.0}/assertpy2/bytes_mixin.py +0 -0
  97. {assertpy2-2.10.0 → assertpy2-2.12.0}/assertpy2/errors.py +0 -0
  98. {assertpy2-2.10.0 → assertpy2-2.12.0}/assertpy2/py.typed +0 -0
  99. {assertpy2-2.10.0 → assertpy2-2.12.0}/assertpy2/pytest_plugin.py +0 -0
  100. {assertpy2-2.10.0 → assertpy2-2.12.0}/docs/assets/diff-equal.png +0 -0
  101. {assertpy2-2.10.0 → assertpy2-2.12.0}/docs/assets/diff-equal.svg +0 -0
  102. {assertpy2-2.10.0 → assertpy2-2.12.0}/docs/assets/diff-gallery.png +0 -0
  103. {assertpy2-2.10.0 → assertpy2-2.12.0}/docs/assets/diff-match.svg +0 -0
  104. {assertpy2-2.10.0 → assertpy2-2.12.0}/docs/assets/diff-sequence.svg +0 -0
  105. {assertpy2-2.10.0 → assertpy2-2.12.0}/docs/assets/diff-set.svg +0 -0
  106. {assertpy2-2.10.0 → assertpy2-2.12.0}/docs/data.md +0 -0
  107. {assertpy2-2.10.0 → assertpy2-2.12.0}/docs/logo-dark.svg +0 -0
  108. {assertpy2-2.10.0 → assertpy2-2.12.0}/docs/logo.svg +0 -0
  109. {assertpy2-2.10.0 → assertpy2-2.12.0}/docs/matchers.md +0 -0
  110. {assertpy2-2.10.0 → assertpy2-2.12.0}/docs/testing.md +0 -0
  111. {assertpy2-2.10.0 → assertpy2-2.12.0}/docs/type-safety.md +0 -0
  112. {assertpy2-2.10.0 → assertpy2-2.12.0}/tests/test_async.py +0 -0
  113. {assertpy2-2.10.0 → assertpy2-2.12.0}/tests/test_behave_matchers.py +0 -0
  114. {assertpy2-2.10.0 → assertpy2-2.12.0}/tests/test_bool.py +0 -0
  115. {assertpy2-2.10.0 → assertpy2-2.12.0}/tests/test_callable.py +0 -0
  116. {assertpy2-2.10.0 → assertpy2-2.12.0}/tests/test_class.py +0 -0
  117. {assertpy2-2.10.0 → assertpy2-2.12.0}/tests/test_collection.py +0 -0
  118. {assertpy2-2.10.0 → assertpy2-2.12.0}/tests/test_core.py +0 -0
  119. {assertpy2-2.10.0 → assertpy2-2.12.0}/tests/test_custom_dict.py +0 -0
  120. {assertpy2-2.10.0 → assertpy2-2.12.0}/tests/test_custom_list.py +0 -0
  121. {assertpy2-2.10.0 → assertpy2-2.12.0}/tests/test_description.py +0 -0
  122. {assertpy2-2.10.0 → assertpy2-2.12.0}/tests/test_dict.py +0 -0
  123. {assertpy2-2.10.0 → assertpy2-2.12.0}/tests/test_errors.py +0 -0
  124. {assertpy2-2.10.0 → assertpy2-2.12.0}/tests/test_expected_exception.py +0 -0
  125. {assertpy2-2.10.0 → assertpy2-2.12.0}/tests/test_expected_warning.py +0 -0
  126. {assertpy2-2.10.0 → assertpy2-2.12.0}/tests/test_extensions.py +0 -0
  127. {assertpy2-2.10.0 → assertpy2-2.12.0}/tests/test_fail.py +0 -0
  128. {assertpy2-2.10.0 → assertpy2-2.12.0}/tests/test_file.py +0 -0
  129. {assertpy2-2.10.0 → assertpy2-2.12.0}/tests/test_grouped_soft.py +0 -0
  130. {assertpy2-2.10.0 → assertpy2-2.12.0}/tests/test_in.py +0 -0
  131. {assertpy2-2.10.0 → assertpy2-2.12.0}/tests/test_json.py +0 -0
  132. {assertpy2-2.10.0 → assertpy2-2.12.0}/tests/test_list.py +0 -0
  133. {assertpy2-2.10.0 → assertpy2-2.12.0}/tests/test_matcher_registry.py +0 -0
  134. {assertpy2-2.10.0 → assertpy2-2.12.0}/tests/test_namedtuple.py +0 -0
  135. {assertpy2-2.10.0 → assertpy2-2.12.0}/tests/test_none.py +0 -0
  136. {assertpy2-2.10.0 → assertpy2-2.12.0}/tests/test_not.py +0 -0
  137. {assertpy2-2.10.0 → assertpy2-2.12.0}/tests/test_numbers.py +0 -0
  138. {assertpy2-2.10.0 → assertpy2-2.12.0}/tests/test_overloads.py +0 -0
  139. {assertpy2-2.10.0 → assertpy2-2.12.0}/tests/test_pipeline.py +0 -0
  140. {assertpy2-2.10.0 → assertpy2-2.12.0}/tests/test_property_based.py +0 -0
  141. {assertpy2-2.10.0 → assertpy2-2.12.0}/tests/test_pytest_plugin.py +0 -0
  142. {assertpy2-2.10.0 → assertpy2-2.12.0}/tests/test_readme.py +0 -0
  143. {assertpy2-2.10.0 → assertpy2-2.12.0}/tests/test_recursive_compare.py +0 -0
  144. {assertpy2-2.10.0 → assertpy2-2.12.0}/tests/test_recursive_compare_attrs.py +0 -0
  145. {assertpy2-2.10.0 → assertpy2-2.12.0}/tests/test_recursive_compare_pydantic.py +0 -0
  146. {assertpy2-2.10.0 → assertpy2-2.12.0}/tests/test_regex_groups.py +0 -0
  147. {assertpy2-2.10.0 → assertpy2-2.12.0}/tests/test_same_as.py +0 -0
  148. {assertpy2-2.10.0 → assertpy2-2.12.0}/tests/test_satisfy.py +0 -0
  149. {assertpy2-2.10.0 → assertpy2-2.12.0}/tests/test_soft.py +0 -0
  150. {assertpy2-2.10.0 → assertpy2-2.12.0}/tests/test_soft_fail.py +0 -0
  151. {assertpy2-2.10.0 → assertpy2-2.12.0}/tests/test_string.py +0 -0
  152. {assertpy2-2.10.0 → assertpy2-2.12.0}/tests/test_structural.py +0 -0
  153. {assertpy2-2.10.0 → assertpy2-2.12.0}/tests/test_traceback.py +0 -0
  154. {assertpy2-2.10.0 → assertpy2-2.12.0}/tests/test_type.py +0 -0
  155. {assertpy2-2.10.0 → assertpy2-2.12.0}/tests/test_warn.py +0 -0
@@ -19,7 +19,7 @@ jobs:
19
19
  strategy:
20
20
  fail-fast: false
21
21
  matrix:
22
- module: [base, numeric, string, contains, collection, dict, date, matchers, helpers]
22
+ module: [base, numeric, string, contains, collection, dict, date, matchers, helpers, _compare, dataframe, json_mixin, snapshot, bytes_mixin, dynamic, extracting, warning, exception, async_assertions, file, behave_matchers]
23
23
  steps:
24
24
  - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
25
25
  with:
@@ -34,7 +34,7 @@ jobs:
34
34
  python-version: "3.14"
35
35
 
36
36
  - name: Install dependencies
37
- run: uv sync --extra json --group mutation
37
+ run: uv sync --extra json --extra behave --group mutation
38
38
 
39
39
  - name: Run mutation testing
40
40
  env:
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: assertpy2
3
- Version: 2.10.0
3
+ Version: 2.12.0
4
4
  Summary: Fluent assertion library for Python with composable matchers, structural matching, and full type safety
5
5
  Project-URL: Homepage, https://github.com/Solganis/assertpy2
6
6
  Project-URL: Repository, https://github.com/Solganis/assertpy2
@@ -31,9 +31,19 @@ Provides-Extra: allure
31
31
  Requires-Dist: allure-pytest>=2.13; extra == 'allure'
32
32
  Provides-Extra: behave
33
33
  Requires-Dist: behave>=1.2.6; 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'
34
38
  Provides-Extra: json
35
39
  Requires-Dist: jsonpath-ng>=1.8; extra == 'json'
36
40
  Requires-Dist: jsonschema>=4.0; extra == 'json'
41
+ Provides-Extra: numpy
42
+ Requires-Dist: numpy>=1.26; extra == 'numpy'
43
+ Provides-Extra: pandas
44
+ Requires-Dist: pandas>=2.0; extra == 'pandas'
45
+ Provides-Extra: polars
46
+ Requires-Dist: polars>=1.0; extra == 'polars'
37
47
  Description-Content-Type: text/markdown
38
48
 
39
49
  <p align="center">
@@ -68,7 +78,7 @@ Description-Content-Type: text/markdown
68
78
 
69
79
  ---
70
80
 
71
- ## [Quick start](https://solganis.github.io/assertpy2/getting-started/)
81
+ <h2 align="center"><a href="https://solganis.github.io/assertpy2/getting-started/">Quick start</a></h2>
72
82
 
73
83
  ```bash
74
84
  pip install assertpy2 # drop-in replacement for assertpy, just change the import
@@ -88,7 +98,7 @@ def test_user():
88
98
 
89
99
  Browse the [full documentation](https://solganis.github.io/assertpy2/) for every assertion, matcher, and integration.
90
100
 
91
- ## [Why fluent assertions?](https://solganis.github.io/assertpy2/comparison/)
101
+ <h2 align="center"><a href="https://solganis.github.io/assertpy2/comparison/">Why fluent assertions?</a></h2>
92
102
 
93
103
  A fluent chain reads as one intent and replaces several bare asserts - and your IDE
94
104
  offers only the [methods that fit the value's type](https://solganis.github.io/assertpy2/type-safety/):
@@ -121,17 +131,21 @@ assertpy2 reports the [exact path to every difference](https://solganis.github.i
121
131
  assert_that(response).is_equal_to(expected)
122
132
  ```
123
133
 
124
- <img src="https://raw.githubusercontent.com/Solganis/assertpy2/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">
134
+ <p align="center">
135
+ <img src="https://raw.githubusercontent.com/Solganis/assertpy2/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">
136
+ </p>
125
137
 
126
- Recursive diffs work for dicts, dataclasses, namedtuples, attrs, and Pydantic models.
138
+ Recursive diffs work for dicts, dataclasses, namedtuples, and Pydantic models.
127
139
  For responses with dynamic fields (IDs, timestamps), validate a subset with
128
140
  [`matches_structure()`](https://solganis.github.io/assertpy2/matchers/#structural-matching) instead of exact equality.
129
141
 
130
142
  The same path-level treatment for dicts, lists, sets, and matcher predicates:
131
143
 
132
- <img src="https://raw.githubusercontent.com/Solganis/assertpy2/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">
144
+ <p align="center">
145
+ <img src="https://raw.githubusercontent.com/Solganis/assertpy2/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">
146
+ </p>
133
147
 
134
- ## [Type-aware autocomplete](https://solganis.github.io/assertpy2/type-safety/)
148
+ <h2 align="center"><a href="https://solganis.github.io/assertpy2/type-safety/">Type-aware autocomplete</a></h2>
135
149
 
136
150
  `assert_that()` uses `@overload` to return type-specific Protocols.
137
151
  Your IDE shows only methods relevant to the value you're testing, not all 100+:
@@ -146,25 +160,25 @@ Your IDE shows only methods relevant to the value you're testing, not all 100+:
146
160
 
147
161
  See the [**Type Safety**](https://solganis.github.io/assertpy2/type-safety/) guide for the full walkthrough.
148
162
 
149
- ---
150
-
151
- ## Features
163
+ <h2 align="center">Features</h2>
152
164
 
153
165
  **Fluent API**
154
166
 
155
167
  - [**Composable matchers**](https://solganis.github.io/assertpy2/matchers/): `match.greater_than(5)`, `match.is_uuid()`, combine with `&`, `|`, `~`. Also work with plain `assert ==`.
156
168
  - [**Structural matching**](https://solganis.github.io/assertpy2/matchers/#structural-matching): `matches_structure()` for declarative dict/API response validation, reporting the exact path to each mismatch on failure.
169
+ - [**Recursive field assertions**](https://solganis.github.io/assertpy2/assertions/#recursive-field-assertions): `all_fields_satisfy()` / `has_no_none_fields()` apply a predicate to every leaf of an object graph, reporting the exact path.
157
170
  - [**Universal negation**](https://solganis.github.io/assertpy2/fluent/#universal-negation): `.not_` inverts any assertion without dedicated `is_not_*` methods.
158
171
  - [**Collection pipeline**](https://solganis.github.io/assertpy2/fluent/#collection-pipeline): `filtered_on()`, `mapped()`, `flat_mapped()`, `first()`, `last()`, `element()`, `single()`.
172
+ - [**Positional & pairwise checks**](https://solganis.github.io/assertpy2/assertions/#lists): `satisfies_exactly()`, `zip_satisfies()`, `contains_only_once()`, `has_same_size_as()`.
159
173
  - [**Fluent chaining**](https://solganis.github.io/assertpy2/fluent/#chaining): write assertions as readable one-liners that chain naturally.
160
174
 
161
175
  **Built-in types**
162
176
 
163
177
  - [Strings](https://solganis.github.io/assertpy2/assertions/#strings), [numbers](https://solganis.github.io/assertpy2/assertions/#numbers), [lists](https://solganis.github.io/assertpy2/assertions/#lists), [tuples](https://solganis.github.io/assertpy2/assertions/#tuples), [sets](https://solganis.github.io/assertpy2/assertions/#sets), [dicts](https://solganis.github.io/assertpy2/assertions/#dicts), [dates](https://solganis.github.io/assertpy2/assertions/#dates), [booleans](https://solganis.github.io/assertpy2/assertions/#booleans), [objects](https://solganis.github.io/assertpy2/assertions/#objects), [bytes](https://solganis.github.io/assertpy2/assertions/#bytes--bytearray), [files](https://solganis.github.io/assertpy2/assertions/#files), [exceptions](https://solganis.github.io/assertpy2/errors/#expected-exceptions).
164
178
  - [**Bytes assertions**](https://solganis.github.io/assertpy2/assertions/#bytes--bytearray): `is_valid_utf8()`, `starts_with_bytes()`, `is_hex_equal_to()`, `decoded_as()` for `bytes`/`bytearray`.
165
- - [**JSON assertions**](https://solganis.github.io/assertpy2/data/#json-path--schema): JSONPath navigation and JSON Schema validation. `pip install assertpy2[json]`.
166
179
  - [**Dynamic assertions**](https://solganis.github.io/assertpy2/assertions/#dynamic-assertions-on-objects): `has_<name>()` for any attribute, property, or zero-argument method.
167
- - [**Dict comparison**](https://solganis.github.io/assertpy2/assertions/#selective-comparison-ignore--include): `is_equal_to()` with `ignore` and `include` for selective key/field matching (dicts, dataclasses, namedtuples, Pydantic models, attrs, plain objects).
180
+ - [**Dict comparison**](https://solganis.github.io/assertpy2/assertions/#selective-comparison-ignore--include): `is_equal_to()` with `ignore` and `include` for selective key/field matching (dicts, dataclasses, namedtuples, Pydantic models, attrs, plain objects), including by regex or type.
181
+ - [**Recursive comparison**](https://solganis.github.io/assertpy2/assertions/#recursive-comparison-tolerance--custom-comparators): `is_equal_to()` with `tolerance` (absolute float tolerance at any depth) or `comparators` (per-type / per-field predicates) for nested structures.
168
182
  - [**Extracting**](https://solganis.github.io/assertpy2/assertions/#extracting-attributes-from-objects): flatten collections on attributes with `filter` and `sort` support.
169
183
 
170
184
  **Testing**
@@ -178,7 +192,7 @@ See the [**Type Safety**](https://solganis.github.io/assertpy2/type-safety/) gui
178
192
 
179
193
  **Type safety**
180
194
 
181
- - [**Type-aware autocomplete**](#type-aware-autocomplete): 9 Protocols, IDE shows only relevant methods per type.
195
+ - [**Type-aware autocomplete**](https://solganis.github.io/assertpy2/type-safety/): 9 Protocols, IDE shows only relevant methods per type.
182
196
  - **py.typed**: `Self` return types, PEP 561 compliant ([PEP 561](https://peps.python.org/pep-0561/)).
183
197
 
184
198
  **Extensibility**
@@ -187,64 +201,17 @@ See the [**Type Safety**](https://solganis.github.io/assertpy2/type-safety/) gui
187
201
  - [**Regex group extraction**](https://solganis.github.io/assertpy2/data/#regex-group-extraction): `extracting_group()` and `matches_with_groups()` for regex captures.
188
202
  - [**Extensions**](https://solganis.github.io/assertpy2/extending/): `add_extension()` for custom assertion methods.
189
203
 
190
- **Integrations**
191
-
192
- - [**Allure**](https://solganis.github.io/assertpy2/integrations/#allure): auto-attach structured diff and actual/expected data to reports. `pip install assertpy2[allure]`.
193
- - [**Behave**](https://solganis.github.io/assertpy2/integrations/#behave): ready-made parameter types for step definitions. `pip install assertpy2[behave]`.
194
-
195
204
  See the [full documentation](https://solganis.github.io/assertpy2/) for all assertion methods, examples, and advanced features.
196
205
 
197
- ---
198
-
199
- ## [Integrations](https://solganis.github.io/assertpy2/integrations/)
200
-
201
- ### Allure
202
-
203
- When `allure-pytest` is installed, the pytest plugin auto-attaches structured failure data to Allure reports as JSON attachments.
204
-
205
- ```bash
206
- pip install assertpy2[allure]
207
- ```
208
-
209
- Three modes controlled via `pytest.ini` (or `pyproject.toml`):
210
-
211
- | Mode | What is attached |
212
- |---|---|
213
- | `diff` (default) | Structured Diff JSON (path-level breakdown) |
214
- | `full` | Structured Diff + actual/expected JSON |
215
- | `off` | Nothing |
216
-
217
- ```toml
218
- # pyproject.toml
219
- [tool.pytest.ini_options]
220
- assertpy2_allure = "full"
221
- ```
222
-
223
- ### Behave
224
-
225
- Ready-made parameter types for Behave step definitions:
226
-
227
- ```bash
228
- pip install assertpy2[behave]
229
- ```
230
-
231
- ```py
232
- # in environment.py or steps/conftest.py
233
- from assertpy2.behave_matchers import register_assertpy_types
234
- register_assertpy_types()
235
- ```
236
-
237
- Then use in step definitions:
238
-
239
- ```py
240
- @given('a user aged {age:PositiveInt}')
241
- def step_impl(context, age):
242
- context.age = age # already validated as int > 0
243
- ```
206
+ <h2 align="center"><a href="https://solganis.github.io/assertpy2/integrations/">Integrations</a></h2>
244
207
 
245
- Available types: `PositiveInt`, `NonNegativeInt`, `PositiveFloat`, `NonEmptyString`, `BoolLike`.
208
+ Optional adapters, each its own extra; full configuration and examples are in the
209
+ [Integrations guide](https://solganis.github.io/assertpy2/integrations/).
246
210
 
247
- See the [Integrations guide](https://solganis.github.io/assertpy2/integrations/) for attachment modes, configuration, and full examples.
211
+ - [**Allure**](https://solganis.github.io/assertpy2/integrations/#allure) (`pip install assertpy2[allure]`): the pytest plugin auto-attaches structured diff and actual/expected data to Allure reports, in three configurable modes.
212
+ - [**Behave**](https://solganis.github.io/assertpy2/integrations/#behave) (`pip install assertpy2[behave]`): ready-made parameter types (`PositiveInt`, `NonEmptyString`, ...) for step definitions like `{age:PositiveInt}`.
213
+ - [**JSON**](https://solganis.github.io/assertpy2/data/#json-path--schema) (`pip install assertpy2[json]`): JSONPath navigation (`at_json_path()`, `has_json_path()`) and JSON Schema validation (`matches_json_schema()`).
214
+ - [**Data frames**](https://solganis.github.io/assertpy2/integrations/#data-frames-and-arrays) (`pip install assertpy2[pandas]` / `[polars]` / `[numpy]`): fluent equality for pandas/polars frames and numpy arrays, carrying each library's own diff.
248
215
 
249
216
  ---
250
217
 
@@ -30,7 +30,7 @@
30
30
 
31
31
  ---
32
32
 
33
- ## [Quick start](https://solganis.github.io/assertpy2/getting-started/)
33
+ <h2 align="center"><a href="https://solganis.github.io/assertpy2/getting-started/">Quick start</a></h2>
34
34
 
35
35
  ```bash
36
36
  pip install assertpy2 # drop-in replacement for assertpy, just change the import
@@ -50,7 +50,7 @@ def test_user():
50
50
 
51
51
  Browse the [full documentation](https://solganis.github.io/assertpy2/) for every assertion, matcher, and integration.
52
52
 
53
- ## [Why fluent assertions?](https://solganis.github.io/assertpy2/comparison/)
53
+ <h2 align="center"><a href="https://solganis.github.io/assertpy2/comparison/">Why fluent assertions?</a></h2>
54
54
 
55
55
  A fluent chain reads as one intent and replaces several bare asserts - and your IDE
56
56
  offers only the [methods that fit the value's type](https://solganis.github.io/assertpy2/type-safety/):
@@ -83,17 +83,21 @@ assertpy2 reports the [exact path to every difference](https://solganis.github.i
83
83
  assert_that(response).is_equal_to(expected)
84
84
  ```
85
85
 
86
- <img src="https://raw.githubusercontent.com/Solganis/assertpy2/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">
86
+ <p align="center">
87
+ <img src="https://raw.githubusercontent.com/Solganis/assertpy2/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">
88
+ </p>
87
89
 
88
- Recursive diffs work for dicts, dataclasses, namedtuples, attrs, and Pydantic models.
90
+ Recursive diffs work for dicts, dataclasses, namedtuples, and Pydantic models.
89
91
  For responses with dynamic fields (IDs, timestamps), validate a subset with
90
92
  [`matches_structure()`](https://solganis.github.io/assertpy2/matchers/#structural-matching) instead of exact equality.
91
93
 
92
94
  The same path-level treatment for dicts, lists, sets, and matcher predicates:
93
95
 
94
- <img src="https://raw.githubusercontent.com/Solganis/assertpy2/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">
96
+ <p align="center">
97
+ <img src="https://raw.githubusercontent.com/Solganis/assertpy2/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">
98
+ </p>
95
99
 
96
- ## [Type-aware autocomplete](https://solganis.github.io/assertpy2/type-safety/)
100
+ <h2 align="center"><a href="https://solganis.github.io/assertpy2/type-safety/">Type-aware autocomplete</a></h2>
97
101
 
98
102
  `assert_that()` uses `@overload` to return type-specific Protocols.
99
103
  Your IDE shows only methods relevant to the value you're testing, not all 100+:
@@ -108,25 +112,25 @@ Your IDE shows only methods relevant to the value you're testing, not all 100+:
108
112
 
109
113
  See the [**Type Safety**](https://solganis.github.io/assertpy2/type-safety/) guide for the full walkthrough.
110
114
 
111
- ---
112
-
113
- ## Features
115
+ <h2 align="center">Features</h2>
114
116
 
115
117
  **Fluent API**
116
118
 
117
119
  - [**Composable matchers**](https://solganis.github.io/assertpy2/matchers/): `match.greater_than(5)`, `match.is_uuid()`, combine with `&`, `|`, `~`. Also work with plain `assert ==`.
118
120
  - [**Structural matching**](https://solganis.github.io/assertpy2/matchers/#structural-matching): `matches_structure()` for declarative dict/API response validation, reporting the exact path to each mismatch on failure.
121
+ - [**Recursive field assertions**](https://solganis.github.io/assertpy2/assertions/#recursive-field-assertions): `all_fields_satisfy()` / `has_no_none_fields()` apply a predicate to every leaf of an object graph, reporting the exact path.
119
122
  - [**Universal negation**](https://solganis.github.io/assertpy2/fluent/#universal-negation): `.not_` inverts any assertion without dedicated `is_not_*` methods.
120
123
  - [**Collection pipeline**](https://solganis.github.io/assertpy2/fluent/#collection-pipeline): `filtered_on()`, `mapped()`, `flat_mapped()`, `first()`, `last()`, `element()`, `single()`.
124
+ - [**Positional & pairwise checks**](https://solganis.github.io/assertpy2/assertions/#lists): `satisfies_exactly()`, `zip_satisfies()`, `contains_only_once()`, `has_same_size_as()`.
121
125
  - [**Fluent chaining**](https://solganis.github.io/assertpy2/fluent/#chaining): write assertions as readable one-liners that chain naturally.
122
126
 
123
127
  **Built-in types**
124
128
 
125
129
  - [Strings](https://solganis.github.io/assertpy2/assertions/#strings), [numbers](https://solganis.github.io/assertpy2/assertions/#numbers), [lists](https://solganis.github.io/assertpy2/assertions/#lists), [tuples](https://solganis.github.io/assertpy2/assertions/#tuples), [sets](https://solganis.github.io/assertpy2/assertions/#sets), [dicts](https://solganis.github.io/assertpy2/assertions/#dicts), [dates](https://solganis.github.io/assertpy2/assertions/#dates), [booleans](https://solganis.github.io/assertpy2/assertions/#booleans), [objects](https://solganis.github.io/assertpy2/assertions/#objects), [bytes](https://solganis.github.io/assertpy2/assertions/#bytes--bytearray), [files](https://solganis.github.io/assertpy2/assertions/#files), [exceptions](https://solganis.github.io/assertpy2/errors/#expected-exceptions).
126
130
  - [**Bytes assertions**](https://solganis.github.io/assertpy2/assertions/#bytes--bytearray): `is_valid_utf8()`, `starts_with_bytes()`, `is_hex_equal_to()`, `decoded_as()` for `bytes`/`bytearray`.
127
- - [**JSON assertions**](https://solganis.github.io/assertpy2/data/#json-path--schema): JSONPath navigation and JSON Schema validation. `pip install assertpy2[json]`.
128
131
  - [**Dynamic assertions**](https://solganis.github.io/assertpy2/assertions/#dynamic-assertions-on-objects): `has_<name>()` for any attribute, property, or zero-argument method.
129
- - [**Dict comparison**](https://solganis.github.io/assertpy2/assertions/#selective-comparison-ignore--include): `is_equal_to()` with `ignore` and `include` for selective key/field matching (dicts, dataclasses, namedtuples, Pydantic models, attrs, plain objects).
132
+ - [**Dict comparison**](https://solganis.github.io/assertpy2/assertions/#selective-comparison-ignore--include): `is_equal_to()` with `ignore` and `include` for selective key/field matching (dicts, dataclasses, namedtuples, Pydantic models, attrs, plain objects), including by regex or type.
133
+ - [**Recursive comparison**](https://solganis.github.io/assertpy2/assertions/#recursive-comparison-tolerance--custom-comparators): `is_equal_to()` with `tolerance` (absolute float tolerance at any depth) or `comparators` (per-type / per-field predicates) for nested structures.
130
134
  - [**Extracting**](https://solganis.github.io/assertpy2/assertions/#extracting-attributes-from-objects): flatten collections on attributes with `filter` and `sort` support.
131
135
 
132
136
  **Testing**
@@ -140,7 +144,7 @@ See the [**Type Safety**](https://solganis.github.io/assertpy2/type-safety/) gui
140
144
 
141
145
  **Type safety**
142
146
 
143
- - [**Type-aware autocomplete**](#type-aware-autocomplete): 9 Protocols, IDE shows only relevant methods per type.
147
+ - [**Type-aware autocomplete**](https://solganis.github.io/assertpy2/type-safety/): 9 Protocols, IDE shows only relevant methods per type.
144
148
  - **py.typed**: `Self` return types, PEP 561 compliant ([PEP 561](https://peps.python.org/pep-0561/)).
145
149
 
146
150
  **Extensibility**
@@ -149,64 +153,17 @@ See the [**Type Safety**](https://solganis.github.io/assertpy2/type-safety/) gui
149
153
  - [**Regex group extraction**](https://solganis.github.io/assertpy2/data/#regex-group-extraction): `extracting_group()` and `matches_with_groups()` for regex captures.
150
154
  - [**Extensions**](https://solganis.github.io/assertpy2/extending/): `add_extension()` for custom assertion methods.
151
155
 
152
- **Integrations**
153
-
154
- - [**Allure**](https://solganis.github.io/assertpy2/integrations/#allure): auto-attach structured diff and actual/expected data to reports. `pip install assertpy2[allure]`.
155
- - [**Behave**](https://solganis.github.io/assertpy2/integrations/#behave): ready-made parameter types for step definitions. `pip install assertpy2[behave]`.
156
-
157
156
  See the [full documentation](https://solganis.github.io/assertpy2/) for all assertion methods, examples, and advanced features.
158
157
 
159
- ---
160
-
161
- ## [Integrations](https://solganis.github.io/assertpy2/integrations/)
162
-
163
- ### Allure
164
-
165
- When `allure-pytest` is installed, the pytest plugin auto-attaches structured failure data to Allure reports as JSON attachments.
166
-
167
- ```bash
168
- pip install assertpy2[allure]
169
- ```
170
-
171
- Three modes controlled via `pytest.ini` (or `pyproject.toml`):
172
-
173
- | Mode | What is attached |
174
- |---|---|
175
- | `diff` (default) | Structured Diff JSON (path-level breakdown) |
176
- | `full` | Structured Diff + actual/expected JSON |
177
- | `off` | Nothing |
178
-
179
- ```toml
180
- # pyproject.toml
181
- [tool.pytest.ini_options]
182
- assertpy2_allure = "full"
183
- ```
184
-
185
- ### Behave
186
-
187
- Ready-made parameter types for Behave step definitions:
188
-
189
- ```bash
190
- pip install assertpy2[behave]
191
- ```
192
-
193
- ```py
194
- # in environment.py or steps/conftest.py
195
- from assertpy2.behave_matchers import register_assertpy_types
196
- register_assertpy_types()
197
- ```
198
-
199
- Then use in step definitions:
200
-
201
- ```py
202
- @given('a user aged {age:PositiveInt}')
203
- def step_impl(context, age):
204
- context.age = age # already validated as int > 0
205
- ```
158
+ <h2 align="center"><a href="https://solganis.github.io/assertpy2/integrations/">Integrations</a></h2>
206
159
 
207
- Available types: `PositiveInt`, `NonNegativeInt`, `PositiveFloat`, `NonEmptyString`, `BoolLike`.
160
+ Optional adapters, each its own extra; full configuration and examples are in the
161
+ [Integrations guide](https://solganis.github.io/assertpy2/integrations/).
208
162
 
209
- See the [Integrations guide](https://solganis.github.io/assertpy2/integrations/) for attachment modes, configuration, and full examples.
163
+ - [**Allure**](https://solganis.github.io/assertpy2/integrations/#allure) (`pip install assertpy2[allure]`): the pytest plugin auto-attaches structured diff and actual/expected data to Allure reports, in three configurable modes.
164
+ - [**Behave**](https://solganis.github.io/assertpy2/integrations/#behave) (`pip install assertpy2[behave]`): ready-made parameter types (`PositiveInt`, `NonEmptyString`, ...) for step definitions like `{age:PositiveInt}`.
165
+ - [**JSON**](https://solganis.github.io/assertpy2/data/#json-path--schema) (`pip install assertpy2[json]`): JSONPath navigation (`at_json_path()`, `has_json_path()`) and JSON Schema validation (`matches_json_schema()`).
166
+ - [**Data frames**](https://solganis.github.io/assertpy2/integrations/#data-frames-and-arrays) (`pip install assertpy2[pandas]` / `[polars]` / `[numpy]`): fluent equality for pandas/polars frames and numpy arrays, carrying each library's own diff.
210
167
 
211
168
  ---
212
169
 
@@ -0,0 +1,136 @@
1
+ """Per-call configuration for tolerant / custom-comparator equality, shared by the equality and diff code.
2
+
3
+ ``is_equal_to`` builds a `_CompareConfig` from its ``tolerance``/``comparators`` kwargs and threads it
4
+ through both the boolean comparison (`HelpersMixin._dict_not_equal()`) and the diff/message rendering
5
+ (`BaseMixin._sub_diff_entries()`, `HelpersMixin._dict_err()`). `_node_decision()` is the single
6
+ switch both sides consult, so a tolerated or comparator-equal leaf is reported in neither. With ``config is
7
+ None`` every helper reproduces the engine's historical ``actual != expected`` behavior exactly.
8
+
9
+ Following the package convention, the impl helpers take unannotated args (the typed public surface lives in
10
+ `assertpy2._typing`); they operate on arbitrary user values whose operators ``numbers.Number`` cannot
11
+ express to the type checker.
12
+ """
13
+
14
+ from __future__ import annotations
15
+
16
+ import math
17
+ import numbers
18
+ import re
19
+ from dataclasses import dataclass
20
+ from typing import TYPE_CHECKING
21
+
22
+ if TYPE_CHECKING:
23
+ from collections.abc import Callable
24
+
25
+
26
+ @dataclass(frozen=True, slots=True, kw_only=True)
27
+ class _CompareConfig:
28
+ """Tolerance and custom comparators for a single ``is_equal_to`` call.
29
+
30
+ ``tolerance`` is an absolute tolerance applied to real-number leaves; ``comparators`` maps a ``type`` or
31
+ an immediate field name to a ``(actual, expected) -> bool`` predicate that owns matching leaves.
32
+ """
33
+
34
+ tolerance: float | None = None
35
+ comparators: dict[object, Callable[[object, object], bool]] | None = None
36
+
37
+
38
+ def _build_compare_config(tolerance, comparators) -> _CompareConfig | None:
39
+ """Validate the ``is_equal_to`` ``tolerance``/``comparators`` kwargs and build a config (``None`` if neither).
40
+
41
+ ``tolerance`` must be a non-negative real number (not ``bool``/``complex``/``NaN``); ``comparators`` must be a
42
+ dict of ``(actual, expected) -> bool`` callables keyed by ``type`` or field name.
43
+ """
44
+ if tolerance is None and comparators is None:
45
+ return None
46
+ if tolerance is not None:
47
+ if isinstance(tolerance, bool) or not isinstance(tolerance, numbers.Number) or isinstance(tolerance, complex):
48
+ raise TypeError("given tolerance arg must be a real number")
49
+ if math.isnan(tolerance):
50
+ raise ValueError("given tolerance arg must not be NaN")
51
+ if tolerance < 0:
52
+ raise ValueError("given tolerance arg must not be negative")
53
+ if comparators is not None:
54
+ if not isinstance(comparators, dict):
55
+ raise TypeError("given comparators arg must be a dict")
56
+ for comparator in comparators.values():
57
+ if not callable(comparator):
58
+ raise TypeError("each comparator must be callable")
59
+ return _CompareConfig(tolerance=tolerance, comparators=comparators)
60
+
61
+
62
+ def _is_real_number(value) -> bool:
63
+ """Return whether ``value`` is a real number eligible for tolerance (excludes ``bool`` and ``complex``).
64
+
65
+ Array/frame-likes are not `numbers.Number`, so they are excluded too - tolerance never triggers
66
+ their element-wise ``==`` that has no single truth value.
67
+ """
68
+ return isinstance(value, numbers.Number) and not isinstance(value, (bool, complex))
69
+
70
+
71
+ def _within_tolerance(actual, expected, tolerance) -> bool:
72
+ """Return whether two real numbers are within ``tolerance`` (absolute); ``NaN`` is never within.
73
+
74
+ Only actual ``float`` operands are checked for ``NaN`` (other reals cannot be ``NaN``), which also keeps
75
+ ``math.isnan`` off arbitrary-precision ``int`` values that would overflow it.
76
+ """
77
+ if isinstance(actual, float) and math.isnan(actual):
78
+ return False
79
+ if isinstance(expected, float) and math.isnan(expected):
80
+ return False
81
+ return abs(actual - expected) <= tolerance
82
+
83
+
84
+ def _resolve_comparator(actual, config: _CompareConfig, *, field):
85
+ """Resolve the comparator owning a leaf: immediate field name first, then exact type, then ``isinstance``.
86
+
87
+ ``field`` is the leaf's immediate key/field name (``None`` for sequence elements and bare scalars, which
88
+ have no name). Returns ``None`` when no comparator applies, so the caller falls back to tolerance / ``==``.
89
+ """
90
+ comparators = config.comparators
91
+ if comparators is None:
92
+ return None
93
+ if field is not None and not isinstance(field, type) and field in comparators:
94
+ return comparators[field]
95
+ if type(actual) in comparators:
96
+ return comparators[type(actual)]
97
+ for key, comparator in comparators.items():
98
+ if isinstance(key, type) and isinstance(actual, key):
99
+ return comparator
100
+ return None
101
+
102
+
103
+ def _node_decision(actual, expected, config: _CompareConfig | None, *, field=None) -> str:
104
+ """Classify a node as ``"equal"``, ``"leaf"`` or ``"recurse"``.
105
+
106
+ With ``config is None`` this is exactly the engine's historical behavior: differing values ``"recurse"``
107
+ (to decompose into a sub-diff), equal values are ``"equal"`` (skipped); ``"leaf"`` never occurs. With a
108
+ config, a matching comparator or tolerance owns the node - it is classified ``"equal"`` or ``"leaf"`` and
109
+ never recursed into.
110
+ """
111
+ if config is not None:
112
+ comparator = _resolve_comparator(actual, config, field=field)
113
+ if comparator is not None:
114
+ return "equal" if comparator(actual, expected) else "leaf"
115
+ if config.tolerance is not None and _is_real_number(actual) and _is_real_number(expected):
116
+ return "equal" if _within_tolerance(actual, expected, config.tolerance) else "leaf"
117
+ return "recurse" if actual != expected else "equal"
118
+
119
+
120
+ def _spec_matches(key, value, specs) -> bool:
121
+ """Return whether a dict ``key``/``value`` matches any ignore/include ``spec``.
122
+
123
+ A spec matches by exact key equality (today's behavior), by a compiled `re.Pattern` searched
124
+ against ``str(key)``, or by a ``type`` the ``value`` is an instance of. Nested-path tuples never match
125
+ here - they are expanded by the recursion in `HelpersMixin._dict_not_equal()`.
126
+ """
127
+ for spec in specs:
128
+ if isinstance(spec, re.Pattern):
129
+ if spec.search(str(key)):
130
+ return True
131
+ elif isinstance(spec, type):
132
+ if isinstance(value, spec):
133
+ return True
134
+ elif spec == key:
135
+ return True
136
+ return False
@@ -1,6 +1,6 @@
1
1
  """Internal compatibility shims.
2
2
 
3
- ``Self`` entered the standard library's :mod:`typing` in Python 3.11; on 3.10 it is provided by
3
+ ``Self`` entered the standard library's `typing` in Python 3.11; on 3.10 it is provided by
4
4
  ``typing_extensions``. Re-exporting it from one place lets every mixin import ``Self`` without repeating
5
5
  the version gate, and lets ``typing_extensions`` be dropped as a runtime dependency on Python 3.11+.
6
6
  """
@@ -5,8 +5,9 @@ from typing import TYPE_CHECKING, Any
5
5
  if TYPE_CHECKING:
6
6
  import logging
7
7
 
8
+ from ._compare import _CompareConfig
8
9
  from ._compat import Self
9
- from .errors import DiffResult
10
+ from .errors import DiffEntry, DiffResult
10
11
 
11
12
 
12
13
  class _MixinBase:
@@ -60,7 +61,7 @@ class _MixinBase:
60
61
 
61
62
  def _is_dict_like(
62
63
  self,
63
- d: object,
64
+ candidate: object,
64
65
  check_keys: bool = ...,
65
66
  check_values: bool = ...,
66
67
  check_getitem: bool = ...,
@@ -68,7 +69,7 @@ class _MixinBase:
68
69
 
69
70
  def _require_dict_like(
70
71
  self,
71
- d: object,
72
+ candidate: object,
72
73
  check_keys: bool = ...,
73
74
  check_values: bool = ...,
74
75
  check_getitem: bool = ...,
@@ -83,6 +84,7 @@ class _MixinBase:
83
84
  other: object,
84
85
  ignore: object = ...,
85
86
  include: object = ...,
87
+ config: _CompareConfig | None = ...,
86
88
  ) -> bool: ...
87
89
 
88
90
  def _dict_err(
@@ -91,11 +93,22 @@ class _MixinBase:
91
93
  other: object,
92
94
  ignore: object = ...,
93
95
  include: object = ...,
96
+ config: _CompareConfig | None = ...,
94
97
  ) -> None: ...
95
98
 
96
99
  @staticmethod
97
100
  def _to_comparable_dict(obj: object) -> dict[str, object] | None: ...
98
101
 
102
+ @staticmethod
103
+ def _sub_diff_entries(
104
+ actual: object,
105
+ expected: object,
106
+ prefix: str,
107
+ *,
108
+ _seen: set[int] | None = ...,
109
+ config: _CompareConfig | None = ...,
110
+ ) -> list[DiffEntry] | None: ...
111
+
99
112
  # NumericMixin class attrs used by HelpersMixin._validate_between_args
100
113
  _NUMERIC_COMPAREABLE: frozenset[type]
101
114
  _NUMERIC_NON_COMPAREABLE: frozenset[type]
@@ -3,7 +3,7 @@ from __future__ import annotations
3
3
  from typing import TYPE_CHECKING
4
4
 
5
5
  if TYPE_CHECKING:
6
- from collections.abc import Callable, Hashable
6
+ from collections.abc import Callable, Hashable, Iterable
7
7
  from pathlib import Path
8
8
  from typing import Any, Protocol
9
9
 
@@ -21,7 +21,13 @@ if TYPE_CHECKING:
21
21
  # BaseMixin
22
22
  def described_as(self, description: str) -> Self: ...
23
23
  def is_equal_to(
24
- self, other: object, *, ignore: _KeySpecs | None = ..., include: _KeySpecs | None = ...
24
+ self,
25
+ other: object,
26
+ *,
27
+ ignore: _KeySpecs | None = ...,
28
+ include: _KeySpecs | None = ...,
29
+ tolerance: float | None = ...,
30
+ comparators: dict[object, Callable[..., bool]] | None = ...,
25
31
  ) -> Self: ...
26
32
  def is_not_equal_to(self, other: object) -> Self: ...
27
33
  def is_same_as(self, other: object) -> Self: ...
@@ -36,6 +42,8 @@ if TYPE_CHECKING:
36
42
  def is_callable(self) -> Self: ...
37
43
  def is_not_callable(self) -> Self: ...
38
44
  def satisfies(self, matcher: Matcher | Callable[..., bool]) -> Self: ...
45
+ def all_fields_satisfy(self, matcher: Matcher | Callable[..., bool]) -> Self: ...
46
+ def has_no_none_fields(self) -> Self: ...
39
47
  # ContainsMixin - universal
40
48
  def is_in(self, *items: object) -> Self: ...
41
49
  def is_not_in(self, *items: object) -> Self: ...
@@ -73,8 +81,11 @@ if TYPE_CHECKING:
73
81
  def does_not_contain_duplicates(self) -> Self: ...
74
82
  def contains_exactly(self, *items: object) -> Self: ...
75
83
  def contains_in_order(self, *items: object) -> Self: ...
84
+ def contains_only_once(self, *items: object) -> Self: ...
85
+ def has_same_size_as(self, other: object) -> Self: ...
76
86
  def is_empty(self) -> Self: ...
77
87
  def is_not_empty(self) -> Self: ...
88
+ def is_subset_of(self, *supersets: object) -> Self: ...
78
89
  # StringMixin - regex
79
90
  def extracting_group(self, pattern: str, group: int | str = ...) -> Self: ...
80
91
  def matches_with_groups(self, pattern: str) -> Self: ...
@@ -138,6 +149,10 @@ if TYPE_CHECKING:
138
149
  def any_satisfy(self, matcher: Matcher | Callable[..., bool]) -> Self: ...
139
150
  def all_satisfy(self, matcher: Matcher | Callable[..., bool]) -> Self: ...
140
151
  def none_satisfy(self, matcher: Matcher | Callable[..., bool]) -> Self: ...
152
+ def satisfies_exactly(self, *matchers: Matcher | Callable[..., bool]) -> Self: ...
153
+ def zip_satisfies(self, other: Iterable[object], predicate: Callable[..., bool]) -> Self: ...
154
+ def contains_only_once(self, *items: object) -> Self: ...
155
+ def has_same_size_as(self, other: object) -> Self: ...
141
156
  # CollectionMixin - pipeline
142
157
  def filtered_on(self, predicate: Callable[..., bool]) -> Self: ...
143
158
  def mapped(self, func: Callable[..., object]) -> Self: ...
@@ -166,6 +181,7 @@ if TYPE_CHECKING:
166
181
  # ContainsMixin
167
182
  def contains(self, *items: object) -> Self: ...
168
183
  def does_not_contain(self, *items: object) -> Self: ...
184
+ def has_same_size_as(self, other: object) -> Self: ...
169
185
  def is_empty(self) -> Self: ...
170
186
  def is_not_empty(self) -> Self: ...
171
187
  # BaseMixin
@@ -209,6 +225,7 @@ if TYPE_CHECKING:
209
225
 
210
226
  # ContainsMixin
211
227
  def contains(self, *items: object) -> Self: ...
228
+ def has_same_size_as(self, other: object) -> Self: ...
212
229
  def is_empty(self) -> Self: ...
213
230
  def is_not_empty(self) -> Self: ...
214
231
  # BytesMixin
@@ -223,8 +240,9 @@ if TYPE_CHECKING:
223
240
  class _InvokedAssertion(_StringAssertion, Protocol):
224
241
  """Assertions available after ``when_called_with()`` captured an exception/warning message.
225
242
 
226
- The captured message is a ``str`` (hence the string assertions); :meth:`returned` pivots to
227
- the value the callable returned. Its static type is unknown, so ``returned()`` exposes the
243
+ The captured message is a ``str`` (hence the string assertions);
244
+ [`returned()`][assertpy2.exception.ExceptionMixin.returned] pivots to the value the callable
245
+ returned. Its static type is unknown, so ``returned()`` exposes the
228
246
  type-agnostic core assertions (``is_equal_to``, ``is_instance_of``, ``satisfies``, ...) -
229
247
  type-safe by construction, never advertising methods that may not apply.
230
248
  """