assertpy2 2.22.0__tar.gz → 2.24.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 (238) hide show
  1. {assertpy2-2.22.0 → assertpy2-2.24.0}/CONTRIBUTING.md +57 -9
  2. {assertpy2-2.22.0 → assertpy2-2.24.0}/PKG-INFO +7 -6
  3. {assertpy2-2.22.0 → assertpy2-2.24.0}/README.md +6 -5
  4. {assertpy2-2.22.0 → assertpy2-2.24.0}/assertpy2/_clustering.py +10 -22
  5. assertpy2-2.24.0/assertpy2/_dangling.py +483 -0
  6. assertpy2-2.24.0/assertpy2/_engine/_builder_check_typing.py +1290 -0
  7. assertpy2-2.24.0/assertpy2/_engine/_capable_typing.py +402 -0
  8. assertpy2-2.24.0/assertpy2/_engine/_check_typing.py +686 -0
  9. {assertpy2-2.22.0 → assertpy2-2.24.0}/assertpy2/_engine/_compare.py +11 -16
  10. {assertpy2-2.22.0 → assertpy2-2.24.0}/assertpy2/_engine/_compat.py +14 -1
  11. {assertpy2-2.22.0 → assertpy2-2.24.0}/assertpy2/_engine/_contract.py +2 -4
  12. {assertpy2-2.22.0 → assertpy2-2.24.0}/assertpy2/_engine/_diff.py +8 -17
  13. {assertpy2-2.22.0 → assertpy2-2.24.0}/assertpy2/_engine/_equality.py +5 -13
  14. {assertpy2-2.22.0 → assertpy2-2.24.0}/assertpy2/_engine/_introspection.py +51 -3
  15. {assertpy2-2.22.0 → assertpy2-2.24.0}/assertpy2/_engine/_membership.py +46 -27
  16. {assertpy2-2.22.0 → assertpy2-2.24.0}/assertpy2/_engine/_mixin_base.py +19 -15
  17. assertpy2-2.24.0/assertpy2/_engine/_operations.py +75 -0
  18. {assertpy2-2.22.0 → assertpy2-2.24.0}/assertpy2/_engine/_ordering.py +6 -13
  19. assertpy2-2.24.0/assertpy2/_engine/_poll_typing.py +2718 -0
  20. {assertpy2-2.22.0 → assertpy2-2.24.0}/assertpy2/_engine/_require.py +36 -14
  21. {assertpy2-2.22.0 → assertpy2-2.24.0}/assertpy2/_engine/_size.py +1 -3
  22. assertpy2-2.24.0/assertpy2/_engine/_typing.py +997 -0
  23. {assertpy2-2.22.0 → assertpy2-2.24.0}/assertpy2/_hints.py +19 -47
  24. {assertpy2-2.22.0 → assertpy2-2.24.0}/assertpy2/_inline.py +1 -2
  25. {assertpy2-2.22.0 → assertpy2-2.24.0}/assertpy2/_matcher_impls.py +88 -102
  26. {assertpy2-2.22.0 → assertpy2-2.24.0}/assertpy2/_satisfies.py +85 -60
  27. {assertpy2-2.22.0 → assertpy2-2.24.0}/assertpy2/_snapshot_codec.py +6 -6
  28. {assertpy2-2.22.0 → assertpy2-2.24.0}/assertpy2/assertpy.py +275 -161
  29. {assertpy2-2.22.0 → assertpy2-2.24.0}/assertpy2/async_assertions.py +24 -58
  30. {assertpy2-2.22.0 → assertpy2-2.24.0}/assertpy2/base.py +44 -32
  31. {assertpy2-2.22.0 → assertpy2-2.24.0}/assertpy2/behave_matchers.py +16 -10
  32. {assertpy2-2.22.0 → assertpy2-2.24.0}/assertpy2/bytes_mixin.py +7 -5
  33. {assertpy2-2.22.0 → assertpy2-2.24.0}/assertpy2/collection.py +69 -43
  34. {assertpy2-2.22.0 → assertpy2-2.24.0}/assertpy2/contains.py +55 -40
  35. {assertpy2-2.22.0 → assertpy2-2.24.0}/assertpy2/dataframe.py +11 -7
  36. {assertpy2-2.22.0 → assertpy2-2.24.0}/assertpy2/date.py +21 -14
  37. {assertpy2-2.22.0 → assertpy2-2.24.0}/assertpy2/dict.py +18 -13
  38. {assertpy2-2.22.0 → assertpy2-2.24.0}/assertpy2/dynamic.py +1 -3
  39. {assertpy2-2.22.0 → assertpy2-2.24.0}/assertpy2/errors.py +48 -9
  40. {assertpy2-2.22.0 → assertpy2-2.24.0}/assertpy2/exception.py +32 -9
  41. {assertpy2-2.22.0 → assertpy2-2.24.0}/assertpy2/extracting.py +24 -29
  42. {assertpy2-2.22.0 → assertpy2-2.24.0}/assertpy2/file.py +14 -6
  43. {assertpy2-2.22.0 → assertpy2-2.24.0}/assertpy2/helpers.py +10 -22
  44. {assertpy2-2.22.0 → assertpy2-2.24.0}/assertpy2/http_mixin.py +5 -11
  45. {assertpy2-2.22.0 → assertpy2-2.24.0}/assertpy2/json_mixin.py +10 -17
  46. {assertpy2-2.22.0 → assertpy2-2.24.0}/assertpy2/matchers.py +11 -23
  47. {assertpy2-2.22.0 → assertpy2-2.24.0}/assertpy2/numeric.py +49 -20
  48. {assertpy2-2.22.0 → assertpy2-2.24.0}/assertpy2/pytest_plugin.py +250 -112
  49. {assertpy2-2.22.0 → assertpy2-2.24.0}/assertpy2/snapshot.py +46 -48
  50. {assertpy2-2.22.0 → assertpy2-2.24.0}/assertpy2/string.py +56 -36
  51. {assertpy2-2.22.0 → assertpy2-2.24.0}/assertpy2/warning.py +5 -4
  52. {assertpy2-2.22.0 → assertpy2-2.24.0}/benchmarks/test_perf.py +155 -59
  53. assertpy2-2.24.0/docs/concepts/stability.md +145 -0
  54. {assertpy2-2.22.0 → assertpy2-2.24.0}/docs/concepts/type-safety.md +124 -64
  55. {assertpy2-2.22.0 → assertpy2-2.24.0}/docs/extending/custom-assertions.md +8 -4
  56. assertpy2-2.24.0/docs/getting-started/comparison.md +280 -0
  57. {assertpy2-2.22.0 → assertpy2-2.24.0}/docs/getting-started/migration.md +1 -1
  58. {assertpy2-2.22.0 → assertpy2-2.24.0}/docs/guides/assertions.md +78 -22
  59. {assertpy2-2.22.0 → assertpy2-2.24.0}/docs/guides/data.md +20 -11
  60. {assertpy2-2.22.0 → assertpy2-2.24.0}/docs/guides/errors.md +107 -53
  61. {assertpy2-2.22.0 → assertpy2-2.24.0}/docs/guides/matchers.md +52 -0
  62. {assertpy2-2.22.0 → assertpy2-2.24.0}/docs/guides/testing.md +23 -13
  63. {assertpy2-2.22.0 → assertpy2-2.24.0}/docs/recipes.md +5 -3
  64. {assertpy2-2.22.0 → assertpy2-2.24.0}/pyproject.toml +44 -3
  65. assertpy2-2.24.0/scripts/generate_check_protocols.py +146 -0
  66. assertpy2-2.24.0/scripts/generate_poll_protocols.py +863 -0
  67. assertpy2-2.24.0/scripts/mutation_report.py +145 -0
  68. assertpy2-2.24.0/tests/api_snapshot.json +5975 -0
  69. {assertpy2-2.22.0 → assertpy2-2.24.0}/tests/api_surface.py +115 -7
  70. assertpy2-2.24.0/tests/conftest.py +99 -0
  71. {assertpy2-2.22.0 → assertpy2-2.24.0}/tests/docs_fixtures.py +43 -11
  72. assertpy2-2.24.0/tests/pyright_baseline.py +108 -0
  73. {assertpy2-2.22.0 → assertpy2-2.24.0}/tests/test_api_compatibility.py +113 -29
  74. {assertpy2-2.22.0 → assertpy2-2.24.0}/tests/test_api_vocabulary.py +5 -13
  75. assertpy2-2.24.0/tests/test_architecture_doc.py +133 -0
  76. {assertpy2-2.22.0 → assertpy2-2.24.0}/tests/test_async.py +16 -41
  77. assertpy2-2.24.0/tests/test_async_predicates.py +157 -0
  78. {assertpy2-2.22.0 → assertpy2-2.24.0}/tests/test_boundary_cases.py +2 -25
  79. {assertpy2-2.22.0 → assertpy2-2.24.0}/tests/test_bytes.py +4 -8
  80. assertpy2-2.24.0/tests/test_capable_protocol.py +578 -0
  81. {assertpy2-2.22.0 → assertpy2-2.24.0}/tests/test_check.py +3 -6
  82. assertpy2-2.24.0/tests/test_check_protocols.py +227 -0
  83. {assertpy2-2.22.0 → assertpy2-2.24.0}/tests/test_class.py +35 -0
  84. {assertpy2-2.22.0 → assertpy2-2.24.0}/tests/test_clustering.py +31 -63
  85. {assertpy2-2.22.0 → assertpy2-2.24.0}/tests/test_collection.py +9 -14
  86. {assertpy2-2.22.0 → assertpy2-2.24.0}/tests/test_core.py +18 -42
  87. {assertpy2-2.22.0 → assertpy2-2.24.0}/tests/test_custom_dict.py +9 -19
  88. {assertpy2-2.22.0 → assertpy2-2.24.0}/tests/test_custom_list.py +3 -6
  89. {assertpy2-2.22.0 → assertpy2-2.24.0}/tests/test_dangling.py +300 -30
  90. {assertpy2-2.22.0 → assertpy2-2.24.0}/tests/test_dataframe.py +27 -7
  91. {assertpy2-2.22.0 → assertpy2-2.24.0}/tests/test_datetime.py +8 -9
  92. {assertpy2-2.22.0 → assertpy2-2.24.0}/tests/test_dict.py +1 -2
  93. {assertpy2-2.22.0 → assertpy2-2.24.0}/tests/test_dict_compare.py +1 -5
  94. {assertpy2-2.22.0 → assertpy2-2.24.0}/tests/test_docs_examples.py +46 -14
  95. {assertpy2-2.22.0 → assertpy2-2.24.0}/tests/test_docs_typing.py +37 -15
  96. {assertpy2-2.22.0 → assertpy2-2.24.0}/tests/test_dynamic.py +2 -4
  97. {assertpy2-2.22.0 → assertpy2-2.24.0}/tests/test_equals.py +8 -24
  98. {assertpy2-2.22.0 → assertpy2-2.24.0}/tests/test_errors.py +41 -9
  99. {assertpy2-2.22.0 → assertpy2-2.24.0}/tests/test_evaluation_core.py +159 -56
  100. {assertpy2-2.22.0 → assertpy2-2.24.0}/tests/test_exception_context.py +1 -2
  101. assertpy2-2.24.0/tests/test_expected_contract.py +181 -0
  102. {assertpy2-2.22.0 → assertpy2-2.24.0}/tests/test_expected_exception.py +49 -29
  103. {assertpy2-2.22.0 → assertpy2-2.24.0}/tests/test_expected_warning.py +3 -14
  104. {assertpy2-2.22.0 → assertpy2-2.24.0}/tests/test_extensions.py +20 -16
  105. {assertpy2-2.22.0 → assertpy2-2.24.0}/tests/test_extracting.py +4 -10
  106. {assertpy2-2.22.0 → assertpy2-2.24.0}/tests/test_file.py +1 -1
  107. {assertpy2-2.22.0 → assertpy2-2.24.0}/tests/test_grouped_soft.py +7 -7
  108. {assertpy2-2.22.0 → assertpy2-2.24.0}/tests/test_hints.py +16 -73
  109. {assertpy2-2.22.0 → assertpy2-2.24.0}/tests/test_http.py +6 -24
  110. {assertpy2-2.22.0 → assertpy2-2.24.0}/tests/test_inline_record.py +3 -4
  111. {assertpy2-2.22.0 → assertpy2-2.24.0}/tests/test_inline_snapshot.py +4 -8
  112. {assertpy2-2.22.0 → assertpy2-2.24.0}/tests/test_iterable_cluster.py +23 -11
  113. {assertpy2-2.22.0 → assertpy2-2.24.0}/tests/test_json.py +1 -2
  114. {assertpy2-2.22.0 → assertpy2-2.24.0}/tests/test_list.py +6 -18
  115. {assertpy2-2.22.0 → assertpy2-2.24.0}/tests/test_match_result.py +69 -0
  116. {assertpy2-2.22.0 → assertpy2-2.24.0}/tests/test_matcher_parity.py +51 -32
  117. {assertpy2-2.22.0 → assertpy2-2.24.0}/tests/test_matcher_registry.py +2 -5
  118. {assertpy2-2.22.0 → assertpy2-2.24.0}/tests/test_matchers.py +25 -20
  119. {assertpy2-2.22.0 → assertpy2-2.24.0}/tests/test_message_elision.py +4 -5
  120. {assertpy2-2.22.0 → assertpy2-2.24.0}/tests/test_not.py +14 -20
  121. {assertpy2-2.22.0 → assertpy2-2.24.0}/tests/test_numbers.py +0 -1
  122. {assertpy2-2.22.0 → assertpy2-2.24.0}/tests/test_openapi_contract.py +6 -7
  123. assertpy2-2.24.0/tests/test_operation_contract.py +404 -0
  124. {assertpy2-2.22.0 → assertpy2-2.24.0}/tests/test_optional_integration_contracts.py +1 -2
  125. assertpy2-2.24.0/tests/test_overload_order.py +165 -0
  126. {assertpy2-2.22.0 → assertpy2-2.24.0}/tests/test_pipeline.py +8 -9
  127. assertpy2-2.24.0/tests/test_poll_protocols.py +254 -0
  128. {assertpy2-2.22.0 → assertpy2-2.24.0}/tests/test_property_attrs.py +3 -5
  129. {assertpy2-2.22.0 → assertpy2-2.24.0}/tests/test_property_based.py +197 -193
  130. {assertpy2-2.22.0 → assertpy2-2.24.0}/tests/test_protocol_parity.py +115 -40
  131. {assertpy2-2.22.0 → assertpy2-2.24.0}/tests/test_public_surface.py +61 -31
  132. {assertpy2-2.22.0 → assertpy2-2.24.0}/tests/test_pyright_baseline.py +55 -11
  133. {assertpy2-2.22.0 → assertpy2-2.24.0}/tests/test_pytest_plugin.py +324 -116
  134. {assertpy2-2.22.0 → assertpy2-2.24.0}/tests/test_recursive_compare.py +1 -28
  135. {assertpy2-2.22.0 → assertpy2-2.24.0}/tests/test_recursive_compare_config.py +47 -66
  136. {assertpy2-2.22.0 → assertpy2-2.24.0}/tests/test_refusals.py +33 -18
  137. {assertpy2-2.22.0 → assertpy2-2.24.0}/tests/test_rich_diff.py +29 -94
  138. {assertpy2-2.22.0 → assertpy2-2.24.0}/tests/test_satisfy.py +2 -6
  139. assertpy2-2.24.0/tests/test_skip_guard.py +115 -0
  140. {assertpy2-2.22.0 → assertpy2-2.24.0}/tests/test_snapshots.py +115 -60
  141. {assertpy2-2.22.0 → assertpy2-2.24.0}/tests/test_soft.py +8 -17
  142. {assertpy2-2.22.0 → assertpy2-2.24.0}/tests/test_structural.py +36 -51
  143. {assertpy2-2.22.0 → assertpy2-2.24.0}/tests/test_surface_conformance.py +27 -37
  144. {assertpy2-2.22.0 → assertpy2-2.24.0}/tests/test_sync_eventually.py +1 -2
  145. {assertpy2-2.22.0 → assertpy2-2.24.0}/tests/test_traceback.py +1 -3
  146. assertpy2-2.24.0/tests/test_type_expression_failures.py +148 -0
  147. {assertpy2-2.22.0 → assertpy2-2.24.0}/tests/test_typing.py +183 -96
  148. assertpy2-2.24.0/tests/test_typing_claims.py +194 -0
  149. assertpy2-2.24.0/tests/test_typing_completeness.py +142 -0
  150. assertpy2-2.24.0/tests/test_typing_conformance.py +489 -0
  151. assertpy2-2.24.0/tests/test_typing_from_a_wheel.py +97 -0
  152. assertpy2-2.24.0/tests/test_typing_http.py +78 -0
  153. assertpy2-2.24.0/tests/test_typing_integrations.py +110 -0
  154. {assertpy2-2.22.0 → assertpy2-2.24.0}/tests/test_typing_negative.py +59 -78
  155. assertpy2-2.24.0/tests/test_vacuity_contract.py +463 -0
  156. {assertpy2-2.22.0 → assertpy2-2.24.0}/tests/test_warn.py +24 -33
  157. {assertpy2-2.22.0 → assertpy2-2.24.0}/tests/typing_cases.py +137 -33
  158. assertpy2-2.24.0/tests/typing_harness.py +107 -0
  159. assertpy2-2.24.0/tests/typing_http.py +75 -0
  160. assertpy2-2.24.0/tests/typing_integrations.py +63 -0
  161. assertpy2-2.24.0/tests/typing_integrations_baseline.py +92 -0
  162. {assertpy2-2.22.0 → assertpy2-2.24.0}/tests/typing_negative_baseline.py +131 -40
  163. assertpy2-2.22.0/assertpy2/_dangling.py +0 -292
  164. assertpy2-2.22.0/assertpy2/_engine/_typing.py +0 -632
  165. assertpy2-2.22.0/docs/concepts/stability.md +0 -87
  166. assertpy2-2.22.0/docs/getting-started/comparison.md +0 -248
  167. assertpy2-2.22.0/tests/api_snapshot.json +0 -3724
  168. assertpy2-2.22.0/tests/conftest.py +0 -35
  169. assertpy2-2.22.0/tests/pyright_baseline.py +0 -68
  170. assertpy2-2.22.0/tests/test_typing_claims.py +0 -81
  171. assertpy2-2.22.0/tests/test_vacuity_contract.py +0 -180
  172. {assertpy2-2.22.0 → assertpy2-2.24.0}/.gitignore +0 -0
  173. {assertpy2-2.22.0 → assertpy2-2.24.0}/LICENSE +0 -0
  174. {assertpy2-2.22.0 → assertpy2-2.24.0}/SECURITY.md +0 -0
  175. {assertpy2-2.22.0 → assertpy2-2.24.0}/assertpy2/__init__.py +0 -0
  176. {assertpy2-2.22.0 → assertpy2-2.24.0}/assertpy2/_engine/__init__.py +0 -0
  177. {assertpy2-2.22.0 → assertpy2-2.24.0}/assertpy2/_engine/_path.py +0 -0
  178. {assertpy2-2.22.0 → assertpy2-2.24.0}/assertpy2/_engine/_text.py +0 -0
  179. {assertpy2-2.22.0 → assertpy2-2.24.0}/assertpy2/outcome.py +0 -0
  180. {assertpy2-2.22.0 → assertpy2-2.24.0}/assertpy2/py.typed +0 -0
  181. {assertpy2-2.22.0 → assertpy2-2.24.0}/docs/assets/diff-equal.png +0 -0
  182. {assertpy2-2.22.0 → assertpy2-2.24.0}/docs/assets/diff-equal.svg +0 -0
  183. {assertpy2-2.22.0 → assertpy2-2.24.0}/docs/assets/diff-gallery.png +0 -0
  184. {assertpy2-2.22.0 → assertpy2-2.24.0}/docs/assets/diff-match.svg +0 -0
  185. {assertpy2-2.22.0 → assertpy2-2.24.0}/docs/assets/diff-sequence.svg +0 -0
  186. {assertpy2-2.22.0 → assertpy2-2.24.0}/docs/assets/diff-set.svg +0 -0
  187. {assertpy2-2.22.0 → assertpy2-2.24.0}/docs/assets/diff-string.svg +0 -0
  188. {assertpy2-2.22.0 → assertpy2-2.24.0}/docs/extending/integrations.md +0 -0
  189. {assertpy2-2.22.0 → assertpy2-2.24.0}/docs/getting-started/quickstart.md +0 -0
  190. {assertpy2-2.22.0 → assertpy2-2.24.0}/docs/guides/fluent.md +0 -0
  191. {assertpy2-2.22.0 → assertpy2-2.24.0}/docs/index.md +0 -0
  192. {assertpy2-2.22.0 → assertpy2-2.24.0}/docs/logo-dark.svg +0 -0
  193. {assertpy2-2.22.0 → assertpy2-2.24.0}/docs/logo.svg +0 -0
  194. {assertpy2-2.22.0 → assertpy2-2.24.0}/docs/reference/async.md +0 -0
  195. {assertpy2-2.22.0 → assertpy2-2.24.0}/docs/reference/bytes.md +0 -0
  196. {assertpy2-2.22.0 → assertpy2-2.24.0}/docs/reference/collections.md +0 -0
  197. {assertpy2-2.22.0 → assertpy2-2.24.0}/docs/reference/containment.md +0 -0
  198. {assertpy2-2.22.0 → assertpy2-2.24.0}/docs/reference/core.md +0 -0
  199. {assertpy2-2.22.0 → assertpy2-2.24.0}/docs/reference/dataframes.md +0 -0
  200. {assertpy2-2.22.0 → assertpy2-2.24.0}/docs/reference/dates.md +0 -0
  201. {assertpy2-2.22.0 → assertpy2-2.24.0}/docs/reference/dicts.md +0 -0
  202. {assertpy2-2.22.0 → assertpy2-2.24.0}/docs/reference/dynamic.md +0 -0
  203. {assertpy2-2.22.0 → assertpy2-2.24.0}/docs/reference/entry-points.md +0 -0
  204. {assertpy2-2.22.0 → assertpy2-2.24.0}/docs/reference/errors.md +0 -0
  205. {assertpy2-2.22.0 → assertpy2-2.24.0}/docs/reference/exceptions.md +0 -0
  206. {assertpy2-2.22.0 → assertpy2-2.24.0}/docs/reference/extracting.md +0 -0
  207. {assertpy2-2.22.0 → assertpy2-2.24.0}/docs/reference/files.md +0 -0
  208. {assertpy2-2.22.0 → assertpy2-2.24.0}/docs/reference/http.md +0 -0
  209. {assertpy2-2.22.0 → assertpy2-2.24.0}/docs/reference/json.md +0 -0
  210. {assertpy2-2.22.0 → assertpy2-2.24.0}/docs/reference/matchers.md +0 -0
  211. {assertpy2-2.22.0 → assertpy2-2.24.0}/docs/reference/numbers.md +0 -0
  212. {assertpy2-2.22.0 → assertpy2-2.24.0}/docs/reference/overview.md +0 -0
  213. {assertpy2-2.22.0 → assertpy2-2.24.0}/docs/reference/snapshots.md +0 -0
  214. {assertpy2-2.22.0 → assertpy2-2.24.0}/docs/reference/strings.md +0 -0
  215. {assertpy2-2.22.0 → assertpy2-2.24.0}/docs/reference/warnings.md +0 -0
  216. {assertpy2-2.22.0 → assertpy2-2.24.0}/docs/stylesheets/extra.css +0 -0
  217. {assertpy2-2.22.0 → assertpy2-2.24.0}/mkdocs.yml +0 -0
  218. {assertpy2-2.22.0 → assertpy2-2.24.0}/mkdocs_hooks.py +0 -0
  219. {assertpy2-2.22.0 → assertpy2-2.24.0}/tests/group_compat.py +0 -0
  220. {assertpy2-2.22.0 → assertpy2-2.24.0}/tests/test_attrs_support.py +0 -0
  221. {assertpy2-2.22.0 → assertpy2-2.24.0}/tests/test_behave_matchers.py +0 -0
  222. {assertpy2-2.22.0 → assertpy2-2.24.0}/tests/test_bool.py +0 -0
  223. {assertpy2-2.22.0 → assertpy2-2.24.0}/tests/test_callable.py +0 -0
  224. {assertpy2-2.22.0 → assertpy2-2.24.0}/tests/test_chaining.py +0 -0
  225. {assertpy2-2.22.0 → assertpy2-2.24.0}/tests/test_description.py +0 -0
  226. {assertpy2-2.22.0 → assertpy2-2.24.0}/tests/test_fail.py +0 -0
  227. {assertpy2-2.22.0 → assertpy2-2.24.0}/tests/test_in.py +0 -0
  228. {assertpy2-2.22.0 → assertpy2-2.24.0}/tests/test_namedtuple.py +0 -0
  229. {assertpy2-2.22.0 → assertpy2-2.24.0}/tests/test_none.py +0 -0
  230. {assertpy2-2.22.0 → assertpy2-2.24.0}/tests/test_overloads.py +0 -0
  231. {assertpy2-2.22.0 → assertpy2-2.24.0}/tests/test_recursive_assertion.py +0 -0
  232. {assertpy2-2.22.0 → assertpy2-2.24.0}/tests/test_recursive_compare_attrs.py +0 -0
  233. {assertpy2-2.22.0 → assertpy2-2.24.0}/tests/test_recursive_compare_pydantic.py +0 -0
  234. {assertpy2-2.22.0 → assertpy2-2.24.0}/tests/test_regex_groups.py +0 -0
  235. {assertpy2-2.22.0 → assertpy2-2.24.0}/tests/test_same_as.py +0 -0
  236. {assertpy2-2.22.0 → assertpy2-2.24.0}/tests/test_soft_fail.py +0 -0
  237. {assertpy2-2.22.0 → assertpy2-2.24.0}/tests/test_string.py +0 -0
  238. {assertpy2-2.22.0 → assertpy2-2.24.0}/tests/test_type.py +0 -0
@@ -23,17 +23,26 @@ Read more about how pulls work on GitHub's [About pull requests](https://help.gi
23
23
  - Python 3.10+
24
24
  - [uv](https://docs.astral.sh/uv/) as the package manager
25
25
 
26
+ ## Before you change a public signature
27
+
28
+ Read [ARCHITECTURE.md](ARCHITECTURE.md). Four files under `assertpy2/_engine/` are generated, the public
29
+ surface is pinned by a recorded snapshot, and neither is discoverable from the files themselves.
30
+
26
31
  ## Verification pipeline
27
32
 
28
33
  Run all checks before submitting a PR. Every step must pass.
29
34
 
30
- Install exactly this set. A bare `uv sync` leaves out the optional libraries several tests import, and
31
- those tests fail rather than skip:
35
+ ### Install
36
+
37
+ A bare `uv sync` leaves out the optional libraries several tests import, and those tests then fail
38
+ rather than skip. Install exactly this set:
32
39
 
33
40
  ```bash
34
41
  uv sync --extra json --extra data --extra inline --group integrations --group docs-examples
35
42
  ```
36
43
 
44
+ ### The main gate
45
+
37
46
  ```bash
38
47
  uv run ruff check .
39
48
  uv run ruff format --check .
@@ -43,17 +52,58 @@ uv run pytest tests/test_docs_examples.py tests/test_typing_claims.py
43
52
  uv run --group docs mkdocs build --strict
44
53
  ```
45
54
 
46
- The public typing surface is checked by three checkers. Run them after the block above, not before:
47
- `uv run --group typecheck` adds the checkers to the same `.venv` and leaves them there, and a coverage
48
- run with them installed collects a different set:
55
+ CI requires 100% code coverage.
56
+
57
+ ### Then once on the supported floor
58
+
59
+ Run the suite on Python 3.10 as well. This is not optional, and the block above does not cover it.
60
+
61
+ A union carries `__name__` from 3.14 onwards and not before. `logging.LoggerAdapter` became
62
+ subscriptable in 3.11. Neither difference is visible to a type checker told to target 3.10, and both
63
+ shipped as defects that every other gate passed.
64
+
65
+ Use a throwaway environment. `uv run --python 3.10` inside the project rebuilds `.venv` for that
66
+ version, and on Windows it can fail partway and leave you without pytest:
67
+
68
+ ```bash
69
+ uv venv /tmp/py310 --python 3.10
70
+
71
+ export VIRTUAL_ENV=/tmp/py310
72
+ uv pip install -e ".[json,data,inline]"
73
+ uv pip install pytest pytest-cov hypothesis ruff
74
+ uv pip install attrs pydantic requests httpx flask
75
+
76
+ /tmp/py310/bin/python -m pytest tests --ignore=tests/test_docs_examples.py -q
77
+ ```
78
+
79
+ `ruff` belongs in that list: the tests that compare a generated file against its generator shell out
80
+ to it, and without it they fail for a reason that has nothing to do with your change.
81
+
82
+ ### The four type checkers
83
+
84
+ Run these after the main gate, not before. `uv run --group typecheck` adds the checkers to the same
85
+ `.venv` and leaves them there, and a coverage run with them installed collects a different set:
49
86
 
50
87
  ```bash
51
88
  uv run --group typecheck mypy --strict --follow-imports=silent tests/test_typing.py
52
- uv run --group typecheck pyright tests/test_typing.py
89
+ PYRIGHT_PYTHON_FORCE_VERSION=1.1.413 uv run --group typecheck pyright --pythonversion 3.14 tests/test_typing.py
90
+ uv run --group typecheck pyrefly check tests/test_typing.py
53
91
  uv run --group typecheck pytest tests/test_pyright_baseline.py
54
92
  ```
55
93
 
56
- Three details that cost time if you meet them the hard way:
94
+ `PYRIGHT_PYTHON_FORCE_VERSION` picks the engine rather than the launcher. The `pyright` distribution on
95
+ PyPI stopped at 1.1.411 on 25 June while npm has shipped 1.1.412 and 1.1.413 since, so the launcher and
96
+ the engine move apart. The baseline is recorded against one engine, and `tests/typing_harness.py` pins
97
+ the same build, so only this direct invocation needs the variable.
98
+
99
+ `--pythonversion 3.14` is not decoration. Pyright reports against the interpreter it finds unless
100
+ told otherwise, and the count moves with it: 174 diagnostics for this package on 3.10 against 169 on
101
+ 3.14.
102
+
103
+ Without it a contributor on the supported floor meets a red baseline that says nothing about their
104
+ change. The baseline test passes the same target itself, so it gives one answer everywhere.
105
+
106
+ ### Three details that cost time if you meet them the hard way
57
107
 
58
108
  - `ty check` is scoped on purpose. Run over the whole tree it reports hundreds of diagnostics from
59
109
  `tests/`, which is full of deliberate negative typing cases: calls that must not type-check.
@@ -63,8 +113,6 @@ Three details that cost time if you meet them the hard way:
63
113
  - `tests/test_docs_examples.py` is run separately because it executes the snippets in `docs/`, which
64
114
  needs the `docs-examples` group and a different collection.
65
115
 
66
- CI requires 100% code coverage.
67
-
68
116
  ## Commit style
69
117
 
70
118
  Use [Conventional Commits](https://www.conventionalcommits.org/): `feat:`, `fix:`, `refactor:`, `test:`, `docs:`, `chore:`, etc.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: assertpy2
3
- Version: 2.22.0
3
+ Version: 2.24.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
@@ -57,7 +57,7 @@ Description-Content-Type: text/markdown
57
57
  <img src="docs/logo.svg" alt="assertpy2" width="280">
58
58
  </picture>
59
59
  <br>
60
- <b>A fully typed fluent assertion library for Python</b><br>
60
+ <b>Fully typed fluent assertion library for Python</b><br>
61
61
  A modern, batteries-included fork of <a href="https://github.com/assertpy/assertpy">assertpy</a>
62
62
  </p>
63
63
 
@@ -68,7 +68,7 @@ Description-Content-Type: text/markdown
68
68
  <a href="https://pypi.org/project/assertpy2/"><img src="https://img.shields.io/pypi/pyversions/assertpy2" alt="Python"></a>
69
69
  <a href="https://pepy.tech/projects/assertpy2"><img src="https://static.pepy.tech/badge/assertpy2/month" alt="Downloads"></a>
70
70
  <br>
71
- <a href="https://solganis.github.io/assertpy2/concepts/type-safety/"><img src="https://img.shields.io/badge/type--checked-ty%20%7C%20mypy%20%7C%20pyright-2ea043" alt="public overloads type-checked by ty, mypy --strict, and pyright with zero suppressions"></a>
71
+ <a href="https://solganis.github.io/assertpy2/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
72
  <a href="https://solganis.github.io/assertpy2/"><img src="https://img.shields.io/badge/docs-online-black" alt="Documentation"></a>
73
73
  <a href="https://scorecard.dev/viewer/?uri=github.com/Solganis/assertpy2"><img src="https://img.shields.io/ossf-scorecard/github.com/Solganis/assertpy2?label=OpenSSF%20Scorecard" alt="OpenSSF Scorecard"></a>
74
74
  </p>
@@ -201,7 +201,7 @@ There are [45 matchers](https://solganis.github.io/assertpy2/guides/matchers/),
201
201
  <h2 align="center"><a href="https://solganis.github.io/assertpy2/concepts/type-safety/">Type-aware autocomplete</a></h2>
202
202
 
203
203
  `assert_that()` uses `@overload` to return type-specific Protocols.<br>
204
- Your IDE shows only methods relevant to the value you're testing, not all 100+:
204
+ Your IDE shows only methods relevant to the value you're testing, not the whole surface:
205
205
 
206
206
  - `assert_that("hello").` &rarr; string methods: `starts_with`, `matches`, `is_alpha`, ...
207
207
  - `assert_that(42).` &rarr; numeric methods: `is_positive`, `is_between`, `is_close_to`, ...
@@ -209,7 +209,7 @@ Your IDE shows only methods relevant to the value you're testing, not all 100+:
209
209
  - `assert_that(my_dict).` &rarr; dict methods: `contains_key`, `contains_entry`, `has_json_path`, ...
210
210
  - `assert_that(b"\x89PNG").` &rarr; bytes methods: `starts_with_bytes`, `is_valid_utf8`, `decoded_as`, ...
211
211
 
212
- 11 type-specific Protocols instead of one `Any`.<br>
212
+ 15 type-specific Protocols instead of one `Any`.<br>
213
213
  Works in PyCharm, VS Code, and any LSP-compatible editor.
214
214
 
215
215
  <h2 align="center"><a href="https://solganis.github.io/assertpy2/concepts/type-safety/#typed-narrowing-with-value">Typed narrowing</a></h2>
@@ -221,7 +221,7 @@ An assertion hands the value back, statically narrowed.
221
221
  <!-- docs-guard: skip -->
222
222
  ```python
223
223
  order = assert_that(repo.find(42)).is_not_none().is_instance_of(PaidOrder).value
224
- order.refund() # statically PaidOrder - verified by ty, mypy, and pyright
224
+ order.refund() # statically PaidOrder - verified by ty, mypy, pyright and pyrefly
225
225
  ```
226
226
 
227
227
  For API tests,
@@ -263,6 +263,7 @@ is open to anything else you build.
263
263
  - [**Structural matching**](https://solganis.github.io/assertpy2/guides/matchers/#structural-matching): `matches_structure()` for declarative dict/API-response validation.
264
264
  - [**Recursive field assertions**](https://solganis.github.io/assertpy2/guides/assertions/#recursive-field-assertions): `all_fields_satisfy()` / `has_no_none_fields()` apply a predicate to every leaf of an object graph.
265
265
  - [**Vacuous-assertion guard**](https://solganis.github.io/assertpy2/guides/assertions/#assertions-that-checked-nothing): `--assertpy2-vacuous` warns when a universal assertion passes over an empty collection, having checked nothing.
266
+ - [**Dangling-assertion detector**](https://solganis.github.io/assertpy2/guides/assertions/#assertions-that-never-ran): `--assertpy2-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.
266
267
  - [**Universal negation**](https://solganis.github.io/assertpy2/guides/fluent/#universal-negation): `.not_` inverts any assertion, no dedicated `is_not_*` methods.
267
268
  - [**Collection pipeline**](https://solganis.github.io/assertpy2/guides/fluent/#collection-pipeline): `filtered_on()`, `mapped()`, `flat_mapped()`, `first()`, `last()`, `element()`, `single()`.
268
269
  - [**Positional & pairwise checks**](https://solganis.github.io/assertpy2/guides/assertions/#lists): `satisfies_exactly()`, `zip_satisfies()`, `contains_only_once()`, `has_same_size_as()`, plus `*_in_any_order` variants.
@@ -5,7 +5,7 @@
5
5
  <img src="docs/logo.svg" alt="assertpy2" width="280">
6
6
  </picture>
7
7
  <br>
8
- <b>A fully typed fluent assertion library for Python</b><br>
8
+ <b>Fully typed fluent assertion library for Python</b><br>
9
9
  A modern, batteries-included fork of <a href="https://github.com/assertpy/assertpy">assertpy</a>
10
10
  </p>
11
11
 
@@ -16,7 +16,7 @@
16
16
  <a href="https://pypi.org/project/assertpy2/"><img src="https://img.shields.io/pypi/pyversions/assertpy2" alt="Python"></a>
17
17
  <a href="https://pepy.tech/projects/assertpy2"><img src="https://static.pepy.tech/badge/assertpy2/month" alt="Downloads"></a>
18
18
  <br>
19
- <a href="https://solganis.github.io/assertpy2/concepts/type-safety/"><img src="https://img.shields.io/badge/type--checked-ty%20%7C%20mypy%20%7C%20pyright-2ea043" alt="public overloads type-checked by ty, mypy --strict, and pyright with zero suppressions"></a>
19
+ <a href="https://solganis.github.io/assertpy2/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>
20
20
  <a href="https://solganis.github.io/assertpy2/"><img src="https://img.shields.io/badge/docs-online-black" alt="Documentation"></a>
21
21
  <a href="https://scorecard.dev/viewer/?uri=github.com/Solganis/assertpy2"><img src="https://img.shields.io/ossf-scorecard/github.com/Solganis/assertpy2?label=OpenSSF%20Scorecard" alt="OpenSSF Scorecard"></a>
22
22
  </p>
@@ -149,7 +149,7 @@ There are [45 matchers](https://solganis.github.io/assertpy2/guides/matchers/),
149
149
  <h2 align="center"><a href="https://solganis.github.io/assertpy2/concepts/type-safety/">Type-aware autocomplete</a></h2>
150
150
 
151
151
  `assert_that()` uses `@overload` to return type-specific Protocols.<br>
152
- Your IDE shows only methods relevant to the value you're testing, not all 100+:
152
+ Your IDE shows only methods relevant to the value you're testing, not the whole surface:
153
153
 
154
154
  - `assert_that("hello").` &rarr; string methods: `starts_with`, `matches`, `is_alpha`, ...
155
155
  - `assert_that(42).` &rarr; numeric methods: `is_positive`, `is_between`, `is_close_to`, ...
@@ -157,7 +157,7 @@ Your IDE shows only methods relevant to the value you're testing, not all 100+:
157
157
  - `assert_that(my_dict).` &rarr; dict methods: `contains_key`, `contains_entry`, `has_json_path`, ...
158
158
  - `assert_that(b"\x89PNG").` &rarr; bytes methods: `starts_with_bytes`, `is_valid_utf8`, `decoded_as`, ...
159
159
 
160
- 11 type-specific Protocols instead of one `Any`.<br>
160
+ 15 type-specific Protocols instead of one `Any`.<br>
161
161
  Works in PyCharm, VS Code, and any LSP-compatible editor.
162
162
 
163
163
  <h2 align="center"><a href="https://solganis.github.io/assertpy2/concepts/type-safety/#typed-narrowing-with-value">Typed narrowing</a></h2>
@@ -169,7 +169,7 @@ An assertion hands the value back, statically narrowed.
169
169
  <!-- docs-guard: skip -->
170
170
  ```python
171
171
  order = assert_that(repo.find(42)).is_not_none().is_instance_of(PaidOrder).value
172
- order.refund() # statically PaidOrder - verified by ty, mypy, and pyright
172
+ order.refund() # statically PaidOrder - verified by ty, mypy, pyright and pyrefly
173
173
  ```
174
174
 
175
175
  For API tests,
@@ -211,6 +211,7 @@ is open to anything else you build.
211
211
  - [**Structural matching**](https://solganis.github.io/assertpy2/guides/matchers/#structural-matching): `matches_structure()` for declarative dict/API-response validation.
212
212
  - [**Recursive field assertions**](https://solganis.github.io/assertpy2/guides/assertions/#recursive-field-assertions): `all_fields_satisfy()` / `has_no_none_fields()` apply a predicate to every leaf of an object graph.
213
213
  - [**Vacuous-assertion guard**](https://solganis.github.io/assertpy2/guides/assertions/#assertions-that-checked-nothing): `--assertpy2-vacuous` warns when a universal assertion passes over an empty collection, having checked nothing.
214
+ - [**Dangling-assertion detector**](https://solganis.github.io/assertpy2/guides/assertions/#assertions-that-never-ran): `--assertpy2-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.
214
215
  - [**Universal negation**](https://solganis.github.io/assertpy2/guides/fluent/#universal-negation): `.not_` inverts any assertion, no dedicated `is_not_*` methods.
215
216
  - [**Collection pipeline**](https://solganis.github.io/assertpy2/guides/fluent/#collection-pipeline): `filtered_on()`, `mapped()`, `flat_mapped()`, `first()`, `last()`, `element()`, `single()`.
216
217
  - [**Positional & pairwise checks**](https://solganis.github.io/assertpy2/guides/assertions/#lists): `satisfies_exactly()`, `zip_satisfies()`, `contains_only_once()`, `has_same_size_as()`, plus `*_in_any_order` variants.
@@ -59,9 +59,7 @@ __tracebackhide__ = True
59
59
  # steps that name a *place* in a value, as opposed to `item`, which names a member of a set
60
60
  _COORDINATE: Final = frozenset({"key", "index", "attr", "line"})
61
61
 
62
- # a position collapses to one placeholder: `users[0].role` and `users[7].role` are the same difference
63
- # reported against two rows, and a reader chasing a common cause wants them in one line. a line number
64
- # is incidental in the same way
62
+ # a position collapses to one placeholder: `users[0].role` and `users[7].role` are one difference on two rows
65
63
  _POSITIONAL: Final = frozenset({"index", "line"})
66
64
  _ANY_POSITION: Final = "[*]"
67
65
 
@@ -190,8 +188,7 @@ def stable_repr(value: object, _seen: frozenset[int] = frozenset()) -> str:
190
188
  try:
191
189
  if isinstance(value, (set, frozenset)):
192
190
  members = sorted(stable_repr(member, inner) for member in value)
193
- # an empty one falls back to `repr`, since `{}` is a dict; the price is that `set()`
194
- # and the `frozenset()` it equals read apart, where two non-empty equal ones read alike
191
+ # an empty one falls back to `repr`: `{}` is a dict, so `set()` and the `frozenset()` it equals read apart
195
192
  return "{" + ", ".join(members) + "}" if value else _safe_repr(value)
196
193
  if isinstance(value, dict):
197
194
  pairs = (f"{stable_repr(key, inner)}: {stable_repr(item, inner)}" for key, item in value.items())
@@ -289,9 +286,7 @@ def is_well_formed(key: Signature) -> bool:
289
286
  )
290
287
  if key.steps:
291
288
  return False
292
- # exactly one of the two, which is what `signature()` sets: the diagnostic replaces the values,
293
- # never joins them. A pair of values only for the kinds whose fields hold the compared values, or
294
- # a `contains` payload would print the `None` that stands for a missing item as somebody's value
289
+ # a pair of values only for the kinds whose fields hold them, or a `contains` payload prints a `None`
295
290
  if key.label:
296
291
  return not key.values
297
292
  return len(key.values) == 2 and key.where in _VALUES_ARE_VALUES
@@ -330,17 +325,14 @@ def clusters(
330
325
  """
331
326
  if total_failures <= 0:
332
327
  return []
333
- # keyed on the node id, not appended: one test can be reported failing more than once, which is
334
- # what `pytest-rerunfailures` does on every retry, and counting its attempts as separate failures
335
- # printed a cluster of six over two tests
328
+ # keyed on the node id: `pytest-rerunfailures` retries printed a cluster of six over two tests
336
329
  grouped: dict[Signature, dict[str, None]] = {}
337
330
  actuals: dict[Signature, dict[str, None]] = {}
338
331
  expecteds: dict[Signature, dict[str, None]] = {}
339
332
  for nodeid, found in recorded:
340
333
  for one in found:
341
334
  grouped.setdefault(one.signature, {})[nodeid] = None
342
- # one past the limit, so a full side is distinguishable from a capped one by its length
343
- # alone. The smallest value is always kept, whichever order the failures came in
335
+ # one past the limit, so a full side is distinguishable from a capped one by its length alone
344
336
  for store, value in ((actuals, one.actual), (expecteds, one.expected)):
345
337
  seen = store.setdefault(one.signature, {})
346
338
  if len(seen) <= _EXAMPLE_LIMIT:
@@ -353,8 +345,7 @@ def clusters(
353
345
  for key, nodeids in grouped.items()
354
346
  if len(nodeids) >= minimum
355
347
  ]
356
- # size first, then the signature itself: two clusters of equal size must not swap places between
357
- # runs, and the signature is the only thing about them that cannot vary
348
+ # size first, then the signature: two clusters of equal size must not swap places between runs
358
349
  large.sort(key=lambda cluster: (-cluster.size, cluster.signature))
359
350
  return large
360
351
 
@@ -479,21 +470,18 @@ def render(
479
470
  lines.append(f"{collect_errors} collection error{'' if one else 's'}, not counted below")
480
471
  for cluster, where in zip(shown, _headings(shown), strict=True):
481
472
  lines.append(_headline(cluster, where, total_failures))
482
- # a cluster keyed on its diagnostic already says the difference in words, and the values under
483
- # it are the ones that vary between its failures rather than the thing they share
473
+ # a cluster keyed on its diagnostic says the difference in words; the values are what varies inside it
484
474
  if cluster.signature.label:
485
475
  lines.append(f" {cluster.signature.label}")
486
476
  else:
487
477
  lines.append(f" actual: {_side(cluster.actuals)}")
488
478
  lines.append(f" expected: {_side(cluster.expecteds)}")
489
479
  if omitted:
490
- # said rather than dropped: five of eleven causes shown silently reads as "these are the
491
- # causes". "more" rather than "smaller", since equal-sized clusters are ordered by signature
480
+ # said rather than dropped: "five of eleven" silently reads as "these are the causes". "more" rather
481
+ # than "smaller", since equal-sized clusters are ordered by signature
492
482
  lines.append(f"{len(omitted)} more cluster{'' if len(omitted) == 1 else 's'} not shown")
493
483
  covered = len({nodeid for cluster in found for nodeid in cluster.nodeids})
494
484
  if covered < total_failures:
495
- # what the number measures, rather than "not clustered": two tests that share a difference under
496
- # a floor of three are related, and the summary declined to print them, which is not the same as
497
- # having found nothing about them
485
+ # what the number measures, not "not clustered": the summary declined to print a pair under the floor
498
486
  lines.append(f"{total_failures - covered} of {total_failures} outside any cluster of {minimum}")
499
487
  return lines