assertpy2 2.12.0__tar.gz → 2.14.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 (162) hide show
  1. assertpy2-2.14.0/.github/dependabot.yml +31 -0
  2. {assertpy2-2.12.0 → assertpy2-2.14.0}/.github/workflows/ci.yml +22 -4
  3. {assertpy2-2.12.0 → assertpy2-2.14.0}/.github/workflows/codeql.yml +1 -1
  4. {assertpy2-2.12.0 → assertpy2-2.14.0}/.github/workflows/docs.yml +2 -2
  5. {assertpy2-2.12.0 → assertpy2-2.14.0}/.github/workflows/mutation.yml +2 -2
  6. {assertpy2-2.12.0 → assertpy2-2.14.0}/.github/workflows/publish.yml +2 -2
  7. {assertpy2-2.12.0 → assertpy2-2.14.0}/.github/workflows/scorecard.yml +1 -1
  8. {assertpy2-2.12.0 → assertpy2-2.14.0}/.github/workflows/zizmor.yml +2 -2
  9. {assertpy2-2.12.0 → assertpy2-2.14.0}/PKG-INFO +2 -3
  10. {assertpy2-2.12.0 → assertpy2-2.14.0}/README.md +0 -1
  11. {assertpy2-2.12.0 → assertpy2-2.14.0}/assertpy2/__init__.py +5 -1
  12. {assertpy2-2.12.0 → assertpy2-2.14.0}/assertpy2/_compare.py +99 -2
  13. assertpy2-2.14.0/assertpy2/_diff.py +346 -0
  14. {assertpy2-2.12.0 → assertpy2-2.14.0}/assertpy2/_introspection.py +14 -1
  15. {assertpy2-2.12.0 → assertpy2-2.14.0}/assertpy2/_mixin_base.py +1 -11
  16. {assertpy2-2.12.0 → assertpy2-2.14.0}/assertpy2/_typing.py +34 -2
  17. {assertpy2-2.12.0 → assertpy2-2.14.0}/assertpy2/assertpy.py +69 -16
  18. assertpy2-2.14.0/assertpy2/async_assertions.py +208 -0
  19. {assertpy2-2.12.0 → assertpy2-2.14.0}/assertpy2/base.py +31 -378
  20. {assertpy2-2.12.0 → assertpy2-2.14.0}/assertpy2/dataframe.py +9 -4
  21. assertpy2-2.14.0/assertpy2/errors.py +166 -0
  22. {assertpy2-2.12.0 → assertpy2-2.14.0}/assertpy2/helpers.py +13 -12
  23. {assertpy2-2.12.0 → assertpy2-2.14.0}/assertpy2/matchers.py +18 -7
  24. {assertpy2-2.12.0 → assertpy2-2.14.0}/assertpy2/pytest_plugin.py +76 -11
  25. {assertpy2-2.12.0 → assertpy2-2.14.0}/assertpy2/snapshot.py +69 -5
  26. {assertpy2-2.12.0 → assertpy2-2.14.0}/docs/assertions.md +5 -1
  27. {assertpy2-2.12.0 → assertpy2-2.14.0}/docs/errors.md +32 -0
  28. {assertpy2-2.12.0 → assertpy2-2.14.0}/docs/integrations.md +38 -5
  29. {assertpy2-2.12.0 → assertpy2-2.14.0}/docs/matchers.md +11 -1
  30. {assertpy2-2.12.0 → assertpy2-2.14.0}/docs/reference/errors.md +12 -0
  31. assertpy2-2.14.0/docs/reference/snapshots.md +11 -0
  32. assertpy2-2.14.0/docs/testing.md +193 -0
  33. {assertpy2-2.12.0 → assertpy2-2.14.0}/mkdocs.yml +1 -1
  34. assertpy2-2.14.0/pyproject.toml +158 -0
  35. assertpy2-2.14.0/tests/test_async.py +482 -0
  36. {assertpy2-2.12.0 → assertpy2-2.14.0}/tests/test_dataframe.py +21 -4
  37. {assertpy2-2.12.0 → assertpy2-2.14.0}/tests/test_dict_compare.py +21 -0
  38. assertpy2-2.14.0/tests/test_equals.py +276 -0
  39. {assertpy2-2.12.0 → assertpy2-2.14.0}/tests/test_errors.py +49 -0
  40. {assertpy2-2.12.0 → assertpy2-2.14.0}/tests/test_extensions.py +65 -0
  41. {assertpy2-2.12.0 → assertpy2-2.14.0}/tests/test_matchers.py +24 -0
  42. {assertpy2-2.12.0 → assertpy2-2.14.0}/tests/test_not.py +5 -0
  43. assertpy2-2.14.0/tests/test_protocol_parity.py +86 -0
  44. {assertpy2-2.12.0 → assertpy2-2.14.0}/tests/test_pytest_plugin.py +170 -14
  45. {assertpy2-2.12.0 → assertpy2-2.14.0}/tests/test_recursive_assertion.py +20 -20
  46. {assertpy2-2.12.0 → assertpy2-2.14.0}/tests/test_rich_diff.py +78 -78
  47. {assertpy2-2.12.0 → assertpy2-2.14.0}/tests/test_snapshots.py +96 -1
  48. {assertpy2-2.12.0 → assertpy2-2.14.0}/tests/test_structural.py +56 -0
  49. {assertpy2-2.12.0 → assertpy2-2.14.0}/tests/test_typing.py +26 -0
  50. {assertpy2-2.12.0 → assertpy2-2.14.0}/tests/test_warn.py +39 -23
  51. {assertpy2-2.12.0 → assertpy2-2.14.0}/uv.lock +237 -69
  52. assertpy2-2.12.0/.github/dependabot.yml +0 -20
  53. assertpy2-2.12.0/assertpy2/async_assertions.py +0 -82
  54. assertpy2-2.12.0/assertpy2/errors.py +0 -51
  55. assertpy2-2.12.0/docs/reference/snapshots.md +0 -5
  56. assertpy2-2.12.0/docs/testing.md +0 -119
  57. assertpy2-2.12.0/pyproject.toml +0 -104
  58. assertpy2-2.12.0/tests/test_async.py +0 -164
  59. assertpy2-2.12.0/tests/test_equals.py +0 -138
  60. {assertpy2-2.12.0 → assertpy2-2.14.0}/.codecov.yml +0 -0
  61. {assertpy2-2.12.0 → assertpy2-2.14.0}/.gitattributes +0 -0
  62. {assertpy2-2.12.0 → assertpy2-2.14.0}/.gitignore +0 -0
  63. {assertpy2-2.12.0 → assertpy2-2.14.0}/CONTRIBUTING.md +0 -0
  64. {assertpy2-2.12.0 → assertpy2-2.14.0}/LICENSE +0 -0
  65. {assertpy2-2.12.0 → assertpy2-2.14.0}/SECURITY.md +0 -0
  66. {assertpy2-2.12.0 → assertpy2-2.14.0}/assertpy2/_compat.py +0 -0
  67. {assertpy2-2.12.0 → assertpy2-2.14.0}/assertpy2/behave_matchers.py +0 -0
  68. {assertpy2-2.12.0 → assertpy2-2.14.0}/assertpy2/bytes_mixin.py +0 -0
  69. {assertpy2-2.12.0 → assertpy2-2.14.0}/assertpy2/collection.py +0 -0
  70. {assertpy2-2.12.0 → assertpy2-2.14.0}/assertpy2/contains.py +0 -0
  71. {assertpy2-2.12.0 → assertpy2-2.14.0}/assertpy2/date.py +0 -0
  72. {assertpy2-2.12.0 → assertpy2-2.14.0}/assertpy2/dict.py +0 -0
  73. {assertpy2-2.12.0 → assertpy2-2.14.0}/assertpy2/dynamic.py +0 -0
  74. {assertpy2-2.12.0 → assertpy2-2.14.0}/assertpy2/exception.py +0 -0
  75. {assertpy2-2.12.0 → assertpy2-2.14.0}/assertpy2/extracting.py +0 -0
  76. {assertpy2-2.12.0 → assertpy2-2.14.0}/assertpy2/file.py +0 -0
  77. {assertpy2-2.12.0 → assertpy2-2.14.0}/assertpy2/json_mixin.py +0 -0
  78. {assertpy2-2.12.0 → assertpy2-2.14.0}/assertpy2/numeric.py +0 -0
  79. {assertpy2-2.12.0 → assertpy2-2.14.0}/assertpy2/py.typed +0 -0
  80. {assertpy2-2.12.0 → assertpy2-2.14.0}/assertpy2/string.py +0 -0
  81. {assertpy2-2.12.0 → assertpy2-2.14.0}/assertpy2/warning.py +0 -0
  82. {assertpy2-2.12.0 → assertpy2-2.14.0}/docs/assets/diff-equal.png +0 -0
  83. {assertpy2-2.12.0 → assertpy2-2.14.0}/docs/assets/diff-equal.svg +0 -0
  84. {assertpy2-2.12.0 → assertpy2-2.14.0}/docs/assets/diff-gallery.png +0 -0
  85. {assertpy2-2.12.0 → assertpy2-2.14.0}/docs/assets/diff-match.svg +0 -0
  86. {assertpy2-2.12.0 → assertpy2-2.14.0}/docs/assets/diff-sequence.svg +0 -0
  87. {assertpy2-2.12.0 → assertpy2-2.14.0}/docs/assets/diff-set.svg +0 -0
  88. {assertpy2-2.12.0 → assertpy2-2.14.0}/docs/comparison.md +0 -0
  89. {assertpy2-2.12.0 → assertpy2-2.14.0}/docs/data.md +0 -0
  90. {assertpy2-2.12.0 → assertpy2-2.14.0}/docs/extending.md +0 -0
  91. {assertpy2-2.12.0 → assertpy2-2.14.0}/docs/fluent.md +0 -0
  92. {assertpy2-2.12.0 → assertpy2-2.14.0}/docs/getting-started.md +0 -0
  93. {assertpy2-2.12.0 → assertpy2-2.14.0}/docs/index.md +0 -0
  94. {assertpy2-2.12.0 → assertpy2-2.14.0}/docs/logo-dark.svg +0 -0
  95. {assertpy2-2.12.0 → assertpy2-2.14.0}/docs/logo.svg +0 -0
  96. {assertpy2-2.12.0 → assertpy2-2.14.0}/docs/migration.md +0 -0
  97. {assertpy2-2.12.0 → assertpy2-2.14.0}/docs/reference/async.md +0 -0
  98. {assertpy2-2.12.0 → assertpy2-2.14.0}/docs/reference/bytes.md +0 -0
  99. {assertpy2-2.12.0 → assertpy2-2.14.0}/docs/reference/collections.md +0 -0
  100. {assertpy2-2.12.0 → assertpy2-2.14.0}/docs/reference/containment.md +0 -0
  101. {assertpy2-2.12.0 → assertpy2-2.14.0}/docs/reference/core.md +0 -0
  102. {assertpy2-2.12.0 → assertpy2-2.14.0}/docs/reference/dataframes.md +0 -0
  103. {assertpy2-2.12.0 → assertpy2-2.14.0}/docs/reference/dates.md +0 -0
  104. {assertpy2-2.12.0 → assertpy2-2.14.0}/docs/reference/dicts.md +0 -0
  105. {assertpy2-2.12.0 → assertpy2-2.14.0}/docs/reference/dynamic.md +0 -0
  106. {assertpy2-2.12.0 → assertpy2-2.14.0}/docs/reference/entry-points.md +0 -0
  107. {assertpy2-2.12.0 → assertpy2-2.14.0}/docs/reference/exceptions.md +0 -0
  108. {assertpy2-2.12.0 → assertpy2-2.14.0}/docs/reference/extracting.md +0 -0
  109. {assertpy2-2.12.0 → assertpy2-2.14.0}/docs/reference/files.md +0 -0
  110. {assertpy2-2.12.0 → assertpy2-2.14.0}/docs/reference/json.md +0 -0
  111. {assertpy2-2.12.0 → assertpy2-2.14.0}/docs/reference/matchers.md +0 -0
  112. {assertpy2-2.12.0 → assertpy2-2.14.0}/docs/reference/numbers.md +0 -0
  113. {assertpy2-2.12.0 → assertpy2-2.14.0}/docs/reference/overview.md +0 -0
  114. {assertpy2-2.12.0 → assertpy2-2.14.0}/docs/reference/strings.md +0 -0
  115. {assertpy2-2.12.0 → assertpy2-2.14.0}/docs/reference/warnings.md +0 -0
  116. {assertpy2-2.12.0 → assertpy2-2.14.0}/docs/stylesheets/extra.css +0 -0
  117. {assertpy2-2.12.0 → assertpy2-2.14.0}/docs/type-safety.md +0 -0
  118. {assertpy2-2.12.0 → assertpy2-2.14.0}/tests/test_behave_matchers.py +0 -0
  119. {assertpy2-2.12.0 → assertpy2-2.14.0}/tests/test_bool.py +0 -0
  120. {assertpy2-2.12.0 → assertpy2-2.14.0}/tests/test_bytes.py +0 -0
  121. {assertpy2-2.12.0 → assertpy2-2.14.0}/tests/test_callable.py +0 -0
  122. {assertpy2-2.12.0 → assertpy2-2.14.0}/tests/test_chaining.py +0 -0
  123. {assertpy2-2.12.0 → assertpy2-2.14.0}/tests/test_class.py +0 -0
  124. {assertpy2-2.12.0 → assertpy2-2.14.0}/tests/test_collection.py +0 -0
  125. {assertpy2-2.12.0 → assertpy2-2.14.0}/tests/test_core.py +0 -0
  126. {assertpy2-2.12.0 → assertpy2-2.14.0}/tests/test_custom_dict.py +0 -0
  127. {assertpy2-2.12.0 → assertpy2-2.14.0}/tests/test_custom_list.py +0 -0
  128. {assertpy2-2.12.0 → assertpy2-2.14.0}/tests/test_datetime.py +0 -0
  129. {assertpy2-2.12.0 → assertpy2-2.14.0}/tests/test_description.py +0 -0
  130. {assertpy2-2.12.0 → assertpy2-2.14.0}/tests/test_dict.py +0 -0
  131. {assertpy2-2.12.0 → assertpy2-2.14.0}/tests/test_dynamic.py +0 -0
  132. {assertpy2-2.12.0 → assertpy2-2.14.0}/tests/test_expected_exception.py +0 -0
  133. {assertpy2-2.12.0 → assertpy2-2.14.0}/tests/test_expected_warning.py +0 -0
  134. {assertpy2-2.12.0 → assertpy2-2.14.0}/tests/test_extracting.py +0 -0
  135. {assertpy2-2.12.0 → assertpy2-2.14.0}/tests/test_fail.py +0 -0
  136. {assertpy2-2.12.0 → assertpy2-2.14.0}/tests/test_file.py +0 -0
  137. {assertpy2-2.12.0 → assertpy2-2.14.0}/tests/test_grouped_soft.py +0 -0
  138. {assertpy2-2.12.0 → assertpy2-2.14.0}/tests/test_in.py +0 -0
  139. {assertpy2-2.12.0 → assertpy2-2.14.0}/tests/test_iterable_cluster.py +0 -0
  140. {assertpy2-2.12.0 → assertpy2-2.14.0}/tests/test_json.py +0 -0
  141. {assertpy2-2.12.0 → assertpy2-2.14.0}/tests/test_list.py +0 -0
  142. {assertpy2-2.12.0 → assertpy2-2.14.0}/tests/test_matcher_registry.py +0 -0
  143. {assertpy2-2.12.0 → assertpy2-2.14.0}/tests/test_mutation_hardening.py +0 -0
  144. {assertpy2-2.12.0 → assertpy2-2.14.0}/tests/test_namedtuple.py +0 -0
  145. {assertpy2-2.12.0 → assertpy2-2.14.0}/tests/test_none.py +0 -0
  146. {assertpy2-2.12.0 → assertpy2-2.14.0}/tests/test_numbers.py +0 -0
  147. {assertpy2-2.12.0 → assertpy2-2.14.0}/tests/test_overloads.py +0 -0
  148. {assertpy2-2.12.0 → assertpy2-2.14.0}/tests/test_pipeline.py +0 -0
  149. {assertpy2-2.12.0 → assertpy2-2.14.0}/tests/test_property_based.py +0 -0
  150. {assertpy2-2.12.0 → assertpy2-2.14.0}/tests/test_readme.py +0 -0
  151. {assertpy2-2.12.0 → assertpy2-2.14.0}/tests/test_recursive_compare.py +0 -0
  152. {assertpy2-2.12.0 → assertpy2-2.14.0}/tests/test_recursive_compare_attrs.py +0 -0
  153. {assertpy2-2.12.0 → assertpy2-2.14.0}/tests/test_recursive_compare_config.py +0 -0
  154. {assertpy2-2.12.0 → assertpy2-2.14.0}/tests/test_recursive_compare_pydantic.py +0 -0
  155. {assertpy2-2.12.0 → assertpy2-2.14.0}/tests/test_regex_groups.py +0 -0
  156. {assertpy2-2.12.0 → assertpy2-2.14.0}/tests/test_same_as.py +0 -0
  157. {assertpy2-2.12.0 → assertpy2-2.14.0}/tests/test_satisfy.py +0 -0
  158. {assertpy2-2.12.0 → assertpy2-2.14.0}/tests/test_soft.py +0 -0
  159. {assertpy2-2.12.0 → assertpy2-2.14.0}/tests/test_soft_fail.py +0 -0
  160. {assertpy2-2.12.0 → assertpy2-2.14.0}/tests/test_string.py +0 -0
  161. {assertpy2-2.12.0 → assertpy2-2.14.0}/tests/test_traceback.py +0 -0
  162. {assertpy2-2.12.0 → assertpy2-2.14.0}/tests/test_type.py +0 -0
@@ -0,0 +1,31 @@
1
+ version: 2
2
+ updates:
3
+ # Python dependencies via uv: the native `uv` ecosystem updates pyproject.toml
4
+ # AND keeps uv.lock in sync (the older `pip` ecosystem left the lock untouched).
5
+ - package-ecosystem: uv
6
+ directory: /
7
+ schedule:
8
+ interval: weekly
9
+ cooldown:
10
+ default-days: 7
11
+ semver-major-days: 7
12
+ commit-message:
13
+ prefix: "chore(deps)"
14
+ groups:
15
+ # One PR/week for all minor+patch bumps; majors stay individual for review.
16
+ python-minor-patch:
17
+ patterns: ["*"]
18
+ update-types: ["minor", "patch"]
19
+
20
+ - package-ecosystem: github-actions
21
+ directory: /
22
+ schedule:
23
+ interval: weekly
24
+ cooldown:
25
+ default-days: 7
26
+ commit-message:
27
+ prefix: "ci(deps)"
28
+ groups:
29
+ actions-minor-patch:
30
+ patterns: ["*"]
31
+ update-types: ["minor", "patch"]
@@ -21,12 +21,17 @@ jobs:
21
21
  os: [ubuntu-latest]
22
22
  python-version: ["3.10", "3.11", "3.12", "3.13", "3.14", "3.15"]
23
23
  include:
24
+ # the Codecov cell also installs every optional integration (pandas/polars/numpy extras plus
25
+ # pydantic/attrs/requests), so the tests the other minimal-env cells skip run here for real
26
+ - os: ubuntu-latest
27
+ python-version: "3.14"
28
+ full-deps: true
24
29
  - os: windows-latest
25
30
  python-version: "3.14"
26
31
  - os: macos-latest
27
32
  python-version: "3.14"
28
33
  steps:
29
- - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
34
+ - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
30
35
  with:
31
36
  persist-credentials: false
32
37
 
@@ -34,14 +39,19 @@ jobs:
34
39
  uses: astral-sh/setup-uv@fac544c07dec837d0ccb6301d7b5580bf5edae39 # v8.2.0
35
40
 
36
41
  - name: Set up Python ${{ matrix.python-version }}
37
- uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
42
+ uses: actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1 # v6.3.0
38
43
  with:
39
44
  python-version: ${{ matrix.python-version }}
40
45
  allow-prereleases: true
41
46
 
42
47
  - name: Install dependencies
48
+ if: '!matrix.full-deps'
43
49
  run: uv sync --extra json
44
50
 
51
+ - name: Install dependencies (all integrations)
52
+ if: matrix.full-deps
53
+ run: uv sync --extra json --extra data --group integrations
54
+
45
55
  - name: Test with coverage
46
56
  run: uv run pytest -v --cov=assertpy2 --cov-report=term-missing --cov-report=xml tests
47
57
 
@@ -55,7 +65,7 @@ jobs:
55
65
  lint:
56
66
  runs-on: ubuntu-latest
57
67
  steps:
58
- - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
68
+ - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
59
69
  with:
60
70
  persist-credentials: false
61
71
 
@@ -63,7 +73,7 @@ jobs:
63
73
  uses: astral-sh/setup-uv@fac544c07dec837d0ccb6301d7b5580bf5edae39 # v8.2.0
64
74
 
65
75
  - name: Set up Python
66
- uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
76
+ uses: actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1 # v6.3.0
67
77
  with:
68
78
  python-version: "3.14"
69
79
 
@@ -84,3 +94,11 @@ jobs:
84
94
 
85
95
  - name: Type check (pyright, public typing surface)
86
96
  run: uv run pyright tests/test_typing.py
97
+
98
+ ci-ok:
99
+ needs: [test, lint]
100
+ if: always()
101
+ runs-on: ubuntu-latest
102
+ steps:
103
+ - name: Verify required jobs succeeded
104
+ run: '[ "${{ needs.test.result }}" = "success" ] && [ "${{ needs.lint.result }}" = "success" ]'
@@ -17,7 +17,7 @@ jobs:
17
17
  permissions:
18
18
  security-events: write
19
19
  steps:
20
- - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
20
+ - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
21
21
  with:
22
22
  persist-credentials: false
23
23
  - uses: github/codeql-action/init@8aad20d150bbac5944a9f9d289da16a4b0d87c1e # v4.36.2
@@ -17,7 +17,7 @@ jobs:
17
17
  build:
18
18
  runs-on: ubuntu-latest
19
19
  steps:
20
- - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
20
+ - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
21
21
  with:
22
22
  persist-credentials: false
23
23
 
@@ -25,7 +25,7 @@ jobs:
25
25
  uses: astral-sh/setup-uv@fac544c07dec837d0ccb6301d7b5580bf5edae39 # v8.2.0
26
26
 
27
27
  - name: Set up Python
28
- uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
28
+ uses: actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1 # v6.3.0
29
29
  with:
30
30
  python-version: "3.14"
31
31
 
@@ -21,7 +21,7 @@ jobs:
21
21
  matrix:
22
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
- - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
24
+ - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
25
25
  with:
26
26
  persist-credentials: false
27
27
 
@@ -29,7 +29,7 @@ jobs:
29
29
  uses: astral-sh/setup-uv@fac544c07dec837d0ccb6301d7b5580bf5edae39 # v8.2.0
30
30
 
31
31
  - name: Set up Python
32
- uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
32
+ uses: actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1 # v6.3.0
33
33
  with:
34
34
  python-version: "3.14"
35
35
 
@@ -16,7 +16,7 @@ jobs:
16
16
  contents: write
17
17
  attestations: write
18
18
  steps:
19
- - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
19
+ - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
20
20
  with:
21
21
  persist-credentials: false
22
22
 
@@ -26,7 +26,7 @@ jobs:
26
26
  enable-cache: false
27
27
 
28
28
  - name: Set up Python
29
- uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
29
+ uses: actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1 # v6.3.0
30
30
  with:
31
31
  python-version: "3.15"
32
32
  allow-prereleases: true
@@ -16,7 +16,7 @@ jobs:
16
16
  security-events: write
17
17
  id-token: write
18
18
  steps:
19
- - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
19
+ - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
20
20
  with:
21
21
  persist-credentials: false
22
22
  - uses: ossf/scorecard-action@4eaacf0543bb3f2c246792bd56e8cdeffafb205a # v2.4.3
@@ -19,7 +19,7 @@ jobs:
19
19
  permissions:
20
20
  security-events: write
21
21
  steps:
22
- - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
22
+ - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
23
23
  with:
24
24
  persist-credentials: false
25
- - uses: zizmorcore/zizmor-action@5f14fd08f7cf1cb1609c1e344975f152c7ee938d # v0.5.6
25
+ - uses: zizmorcore/zizmor-action@192e21d79ab29983730a13d1382995c2307fbcaa # v0.5.7
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: assertpy2
3
- Version: 2.12.0
3
+ Version: 2.14.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
@@ -9,7 +9,7 @@ Author-email: Justin Shacklette <justin@saturnboy.com>
9
9
  Maintainer-email: Solganis <solganis.dev@gmail.com>
10
10
  License-Expression: BSD-3-Clause
11
11
  License-File: LICENSE
12
- Keywords: assert,assert_that,assertion,assertthat,pytest,test,testing,unittest
12
+ Keywords: assert,assert_that,assertion,assertpy,assertthat,fluent,matchers,pytest,test,testing,type-safe,unittest
13
13
  Classifier: Development Status :: 5 - Production/Stable
14
14
  Classifier: Intended Audience :: Developers
15
15
  Classifier: License :: OSI Approved :: BSD License
@@ -188,7 +188,6 @@ See the [**Type Safety**](https://solganis.github.io/assertpy2/type-safety/) gui
188
188
  - [**Structured errors**](https://solganis.github.io/assertpy2/errors/#structured-errors): `AssertionFailure` with `.actual`, `.expected`, `.diff` attributes.
189
189
  - [**Rich pytest diffs**](https://solganis.github.io/assertpy2/errors/#rich-pytest-diffs): recursive structural diffs for lists, sets, strings, dicts, dataclasses, namedtuples, Pydantic models, and matcher-based assertions (`matches_structure()`, `satisfies()`, `each()`). Circular reference protection.
190
190
  - [**Snapshot testing**](https://solganis.github.io/assertpy2/testing/#snapshot-testing): store and compare data structures in JSON format.
191
- - **Property-based tested**: comparison, selective-diff, matcher algebra, and collection logic are checked with [Hypothesis](https://hypothesis.readthedocs.io) against reference semantics, on top of 100% branch coverage.
192
191
 
193
192
  **Type safety**
194
193
 
@@ -140,7 +140,6 @@ See the [**Type Safety**](https://solganis.github.io/assertpy2/type-safety/) gui
140
140
  - [**Structured errors**](https://solganis.github.io/assertpy2/errors/#structured-errors): `AssertionFailure` with `.actual`, `.expected`, `.diff` attributes.
141
141
  - [**Rich pytest diffs**](https://solganis.github.io/assertpy2/errors/#rich-pytest-diffs): recursive structural diffs for lists, sets, strings, dicts, dataclasses, namedtuples, Pydantic models, and matcher-based assertions (`matches_structure()`, `satisfies()`, `each()`). Circular reference protection.
142
142
  - [**Snapshot testing**](https://solganis.github.io/assertpy2/testing/#snapshot-testing): store and compare data structures in JSON format.
143
- - **Property-based tested**: comparison, selective-diff, matcher algebra, and collection logic are checked with [Hypothesis](https://hypothesis.readthedocs.io) against reference semantics, on top of 100% branch coverage.
144
143
 
145
144
  **Type safety**
146
145
 
@@ -13,9 +13,10 @@ from .assertpy import (
13
13
  soft_fail,
14
14
  )
15
15
  from .async_assertions import AsyncAssertionBuilder
16
- from .errors import AssertionFailure, DiffEntry, DiffResult
16
+ from .errors import AssertionFailure, DiffEntry, DiffResult, PollSample, PollTrace
17
17
  from .file import contents_of
18
18
  from .matchers import Matcher, clear_custom_matchers, match, register_matcher, unregister_matcher
19
+ from .snapshot import SnapshotCreatedWarning
19
20
 
20
21
  __all__ = [
21
22
  "AssertionFailure",
@@ -24,6 +25,9 @@ __all__ = [
24
25
  "DiffResult",
25
26
  "Matcher",
26
27
  "NegatedBuilder",
28
+ "PollSample",
29
+ "PollTrace",
30
+ "SnapshotCreatedWarning",
27
31
  "SoftAssertionCollector",
28
32
  "WarningLoggingAdapter",
29
33
  "__version__",
@@ -2,7 +2,7 @@
2
2
 
3
3
  ``is_equal_to`` builds a `_CompareConfig` from its ``tolerance``/``comparators`` kwargs and threads it
4
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
5
+ (`assertpy2._diff._sub_diff_entries()`, `HelpersMixin._dict_err()`). `_node_decision()` is the single
6
6
  switch both sides consult, so a tolerated or comparator-equal leaf is reported in neither. With ``config is
7
7
  None`` every helper reproduces the engine's historical ``actual != expected`` behavior exactly.
8
8
 
@@ -13,12 +13,15 @@ express to the type checker.
13
13
 
14
14
  from __future__ import annotations
15
15
 
16
+ import dataclasses
16
17
  import math
17
18
  import numbers
18
19
  import re
19
20
  from dataclasses import dataclass
20
21
  from typing import TYPE_CHECKING
21
22
 
23
+ from ._introspection import is_mapping_like, is_model_dump_object
24
+
22
25
  if TYPE_CHECKING:
23
26
  from collections.abc import Callable
24
27
 
@@ -59,6 +62,100 @@ def _build_compare_config(tolerance, comparators) -> _CompareConfig | None:
59
62
  return _CompareConfig(tolerance=tolerance, comparators=comparators)
60
63
 
61
64
 
65
+ def _ambiguous_array_operand(value: object, other: object) -> object | None:
66
+ """Return the array/frame-like operand whose ``==`` has no single truth value, else ``None``.
67
+
68
+ numpy/pandas/polars containers expose ``__array__`` and compare element-wise, so ``bool(a == b)``
69
+ raises rather than yielding one bool (and a ``DataFrame`` also quacks dict-like, which would otherwise
70
+ mis-dispatch the comparison). The ``__array__`` gate keeps the extra comparison off the hot path; the
71
+ truth test is actually attempted, so 0-d / scalar array values (which *are* truth-testable) pass
72
+ through unchanged.
73
+ """
74
+ for candidate, counterpart in ((value, other), (other, value)):
75
+ if hasattr(candidate, "__array__"):
76
+ try:
77
+ bool(candidate == counterpart)
78
+ except (ValueError, TypeError):
79
+ return candidate
80
+ return None
81
+
82
+
83
+ def _array_equality_error(method: str, operand: object) -> TypeError:
84
+ """Build the actionable error raised when ``method`` is given an element-wise array/frame-like."""
85
+ return TypeError(
86
+ f"{method}() cannot directly compare <{type(operand).__name__}>: its '==' is element-wise and has"
87
+ " no single truth value. Compare the value's own equality (e.g."
88
+ " assert_that(actual.equals(expected)).is_true()), assert on extracted scalars (columns, shape,"
89
+ " length), or use satisfies(...) with an explicit predicate."
90
+ )
91
+
92
+
93
+ def _find_ambiguous_operand(actual, expected, _seen=None):
94
+ """Locate the array/frame-like member that broke a comparison, walking the diff engine's containers.
95
+
96
+ Cold error-path only; ``None`` means the error was not array-caused and must be re-raised unchanged.
97
+ """
98
+ if _seen is None:
99
+ _seen = set()
100
+ pair = (id(actual), id(expected))
101
+ if pair in _seen:
102
+ return None
103
+ _seen = _seen | {pair}
104
+ operand = _ambiguous_array_operand(actual, expected)
105
+ if operand is not None:
106
+ return operand
107
+ if is_mapping_like(actual) and is_mapping_like(expected):
108
+ expected_keys = set(expected)
109
+ for key in actual:
110
+ if key in expected_keys:
111
+ found = _find_ambiguous_operand(actual[key], expected[key], _seen)
112
+ if found is not None:
113
+ return found
114
+ return None
115
+ if (
116
+ dataclasses.is_dataclass(actual)
117
+ and not isinstance(actual, type)
118
+ and dataclasses.is_dataclass(expected)
119
+ and not isinstance(expected, type)
120
+ ):
121
+ for field in dataclasses.fields(actual):
122
+ found = _find_ambiguous_operand(getattr(actual, field.name), getattr(expected, field.name, None), _seen)
123
+ if found is not None:
124
+ return found
125
+ return None
126
+ if is_model_dump_object(actual) and is_model_dump_object(expected):
127
+ return _find_ambiguous_operand(actual.model_dump(), expected.model_dump(), _seen)
128
+ if isinstance(actual, (list, tuple)) and isinstance(expected, (list, tuple)):
129
+ for actual_item, expected_item in zip(actual, expected, strict=False):
130
+ found = _find_ambiguous_operand(actual_item, expected_item, _seen)
131
+ if found is not None:
132
+ return found
133
+ return None
134
+
135
+
136
+ def _guarded_not_equal(actual, expected, *, method="is_equal_to") -> bool:
137
+ """``bool(actual != expected)``, converting the ambiguity raised from *inside* a container's ``==``
138
+ (where the top-level operand gate cannot see the array member) into the actionable ``TypeError``."""
139
+ try:
140
+ return bool(actual != expected)
141
+ except (ValueError, TypeError) as error:
142
+ operand = _find_ambiguous_operand(actual, expected)
143
+ if operand is None:
144
+ raise
145
+ raise _array_equality_error(method, operand) from error
146
+
147
+
148
+ def _guarded_equal(actual, expected, *, method) -> bool:
149
+ """``bool(actual == expected)`` with the same nested array/frame-like guard as `_guarded_not_equal`."""
150
+ try:
151
+ return bool(actual == expected)
152
+ except (ValueError, TypeError) as error:
153
+ operand = _find_ambiguous_operand(actual, expected)
154
+ if operand is None:
155
+ raise
156
+ raise _array_equality_error(method, operand) from error
157
+
158
+
62
159
  def _is_real_number(value) -> bool:
63
160
  """Return whether ``value`` is a real number eligible for tolerance (excludes ``bool`` and ``complex``).
64
161
 
@@ -114,7 +211,7 @@ def _node_decision(actual, expected, config: _CompareConfig | None, *, field=Non
114
211
  return "equal" if comparator(actual, expected) else "leaf"
115
212
  if config.tolerance is not None and _is_real_number(actual) and _is_real_number(expected):
116
213
  return "equal" if _within_tolerance(actual, expected, config.tolerance) else "leaf"
117
- return "recurse" if actual != expected else "equal"
214
+ return "recurse" if _guarded_not_equal(actual, expected) else "equal"
118
215
 
119
216
 
120
217
  def _spec_matches(key, value, specs) -> bool: