assertpy2 2.11.0__tar.gz → 2.13.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 (156) hide show
  1. assertpy2-2.13.0/.github/dependabot.yml +31 -0
  2. {assertpy2-2.11.0 → assertpy2-2.13.0}/.github/workflows/ci.yml +22 -4
  3. {assertpy2-2.11.0 → assertpy2-2.13.0}/.github/workflows/codeql.yml +1 -1
  4. {assertpy2-2.11.0 → assertpy2-2.13.0}/.github/workflows/docs.yml +2 -2
  5. {assertpy2-2.11.0 → assertpy2-2.13.0}/.github/workflows/mutation.yml +4 -4
  6. {assertpy2-2.11.0 → assertpy2-2.13.0}/.github/workflows/publish.yml +2 -2
  7. {assertpy2-2.11.0 → assertpy2-2.13.0}/.github/workflows/scorecard.yml +1 -1
  8. {assertpy2-2.11.0 → assertpy2-2.13.0}/.github/workflows/zizmor.yml +2 -2
  9. {assertpy2-2.11.0 → assertpy2-2.13.0}/PKG-INFO +7 -8
  10. {assertpy2-2.11.0 → assertpy2-2.13.0}/README.md +5 -6
  11. {assertpy2-2.11.0 → assertpy2-2.13.0}/assertpy2/__init__.py +2 -0
  12. assertpy2-2.13.0/assertpy2/_compare.py +222 -0
  13. {assertpy2-2.11.0 → assertpy2-2.13.0}/assertpy2/_compat.py +1 -1
  14. {assertpy2-2.11.0 → assertpy2-2.13.0}/assertpy2/_mixin_base.py +11 -3
  15. {assertpy2-2.11.0 → assertpy2-2.13.0}/assertpy2/_typing.py +39 -6
  16. {assertpy2-2.11.0 → assertpy2-2.13.0}/assertpy2/assertpy.py +72 -49
  17. {assertpy2-2.11.0 → assertpy2-2.13.0}/assertpy2/async_assertions.py +36 -5
  18. {assertpy2-2.11.0 → assertpy2-2.13.0}/assertpy2/base.py +440 -157
  19. {assertpy2-2.11.0 → assertpy2-2.13.0}/assertpy2/collection.py +44 -13
  20. {assertpy2-2.11.0 → assertpy2-2.13.0}/assertpy2/contains.py +77 -25
  21. {assertpy2-2.11.0 → assertpy2-2.13.0}/assertpy2/dataframe.py +5 -4
  22. {assertpy2-2.11.0 → assertpy2-2.13.0}/assertpy2/date.py +23 -19
  23. {assertpy2-2.11.0 → assertpy2-2.13.0}/assertpy2/dict.py +16 -16
  24. {assertpy2-2.11.0 → assertpy2-2.13.0}/assertpy2/dynamic.py +3 -3
  25. {assertpy2-2.11.0 → assertpy2-2.13.0}/assertpy2/exception.py +21 -16
  26. {assertpy2-2.11.0 → assertpy2-2.13.0}/assertpy2/extracting.py +18 -18
  27. {assertpy2-2.11.0 → assertpy2-2.13.0}/assertpy2/file.py +16 -16
  28. {assertpy2-2.11.0 → assertpy2-2.13.0}/assertpy2/helpers.py +106 -79
  29. {assertpy2-2.11.0 → assertpy2-2.13.0}/assertpy2/json_mixin.py +6 -6
  30. {assertpy2-2.11.0 → assertpy2-2.13.0}/assertpy2/matchers.py +44 -12
  31. {assertpy2-2.11.0 → assertpy2-2.13.0}/assertpy2/numeric.py +40 -39
  32. {assertpy2-2.11.0 → assertpy2-2.13.0}/assertpy2/snapshot.py +99 -40
  33. {assertpy2-2.11.0 → assertpy2-2.13.0}/assertpy2/string.py +31 -30
  34. {assertpy2-2.11.0 → assertpy2-2.13.0}/assertpy2/warning.py +18 -16
  35. {assertpy2-2.11.0 → assertpy2-2.13.0}/docs/assertions.md +68 -0
  36. {assertpy2-2.11.0 → assertpy2-2.13.0}/docs/comparison.md +9 -5
  37. {assertpy2-2.11.0 → assertpy2-2.13.0}/docs/errors.md +1 -1
  38. {assertpy2-2.11.0 → assertpy2-2.13.0}/docs/extending.md +3 -0
  39. {assertpy2-2.11.0 → assertpy2-2.13.0}/docs/fluent.md +7 -1
  40. {assertpy2-2.11.0 → assertpy2-2.13.0}/docs/getting-started.md +2 -1
  41. assertpy2-2.13.0/docs/index.md +103 -0
  42. {assertpy2-2.11.0 → assertpy2-2.13.0}/docs/integrations.md +5 -2
  43. {assertpy2-2.11.0 → assertpy2-2.13.0}/docs/matchers.md +6 -1
  44. {assertpy2-2.11.0 → assertpy2-2.13.0}/docs/migration.md +1 -1
  45. assertpy2-2.13.0/docs/reference/async.md +13 -0
  46. assertpy2-2.13.0/docs/reference/bytes.md +5 -0
  47. assertpy2-2.13.0/docs/reference/collections.md +5 -0
  48. assertpy2-2.13.0/docs/reference/containment.md +5 -0
  49. assertpy2-2.13.0/docs/reference/core.md +6 -0
  50. assertpy2-2.13.0/docs/reference/dataframes.md +5 -0
  51. assertpy2-2.13.0/docs/reference/dates.md +5 -0
  52. assertpy2-2.13.0/docs/reference/dicts.md +5 -0
  53. assertpy2-2.13.0/docs/reference/dynamic.md +6 -0
  54. assertpy2-2.13.0/docs/reference/entry-points.md +47 -0
  55. assertpy2-2.13.0/docs/reference/errors.md +23 -0
  56. assertpy2-2.13.0/docs/reference/exceptions.md +5 -0
  57. assertpy2-2.13.0/docs/reference/extracting.md +5 -0
  58. assertpy2-2.13.0/docs/reference/files.md +11 -0
  59. assertpy2-2.13.0/docs/reference/json.md +5 -0
  60. assertpy2-2.13.0/docs/reference/matchers.md +7 -0
  61. assertpy2-2.13.0/docs/reference/numbers.md +5 -0
  62. assertpy2-2.13.0/docs/reference/overview.md +21 -0
  63. assertpy2-2.13.0/docs/reference/snapshots.md +11 -0
  64. assertpy2-2.13.0/docs/reference/strings.md +6 -0
  65. assertpy2-2.13.0/docs/reference/warnings.md +5 -0
  66. assertpy2-2.13.0/docs/stylesheets/extra.css +13 -0
  67. {assertpy2-2.11.0 → assertpy2-2.13.0}/docs/testing.md +35 -6
  68. assertpy2-2.13.0/mkdocs.yml +141 -0
  69. assertpy2-2.13.0/pyproject.toml +158 -0
  70. {assertpy2-2.11.0 → assertpy2-2.13.0}/tests/test_async.py +107 -0
  71. {assertpy2-2.11.0 → assertpy2-2.13.0}/tests/test_bytes.py +20 -0
  72. {assertpy2-2.11.0 → assertpy2-2.13.0}/tests/test_chaining.py +3 -3
  73. {assertpy2-2.11.0 → assertpy2-2.13.0}/tests/test_dataframe.py +13 -0
  74. {assertpy2-2.11.0 → assertpy2-2.13.0}/tests/test_datetime.py +120 -120
  75. {assertpy2-2.11.0 → assertpy2-2.13.0}/tests/test_dict_compare.py +18 -18
  76. {assertpy2-2.11.0 → assertpy2-2.13.0}/tests/test_dynamic.py +17 -0
  77. {assertpy2-2.11.0 → assertpy2-2.13.0}/tests/test_equals.py +129 -0
  78. {assertpy2-2.11.0 → assertpy2-2.13.0}/tests/test_extracting.py +20 -0
  79. assertpy2-2.13.0/tests/test_iterable_cluster.py +201 -0
  80. {assertpy2-2.11.0 → assertpy2-2.13.0}/tests/test_matchers.py +27 -3
  81. {assertpy2-2.11.0 → assertpy2-2.13.0}/tests/test_mutation_hardening.py +32 -0
  82. assertpy2-2.13.0/tests/test_recursive_assertion.py +194 -0
  83. assertpy2-2.13.0/tests/test_recursive_compare_config.py +274 -0
  84. {assertpy2-2.11.0 → assertpy2-2.13.0}/tests/test_snapshots.py +77 -1
  85. {assertpy2-2.11.0 → assertpy2-2.13.0}/tests/test_structural.py +25 -0
  86. {assertpy2-2.11.0 → assertpy2-2.13.0}/tests/test_typing.py +18 -0
  87. {assertpy2-2.11.0 → assertpy2-2.13.0}/uv.lock +354 -128
  88. assertpy2-2.11.0/.github/dependabot.yml +0 -20
  89. assertpy2-2.11.0/docs/index.md +0 -55
  90. assertpy2-2.11.0/mkdocs.yml +0 -82
  91. assertpy2-2.11.0/pyproject.toml +0 -100
  92. {assertpy2-2.11.0 → assertpy2-2.13.0}/.codecov.yml +0 -0
  93. {assertpy2-2.11.0 → assertpy2-2.13.0}/.gitattributes +0 -0
  94. {assertpy2-2.11.0 → assertpy2-2.13.0}/.gitignore +0 -0
  95. {assertpy2-2.11.0 → assertpy2-2.13.0}/CONTRIBUTING.md +0 -0
  96. {assertpy2-2.11.0 → assertpy2-2.13.0}/LICENSE +0 -0
  97. {assertpy2-2.11.0 → assertpy2-2.13.0}/SECURITY.md +0 -0
  98. {assertpy2-2.11.0 → assertpy2-2.13.0}/assertpy2/_introspection.py +0 -0
  99. {assertpy2-2.11.0 → assertpy2-2.13.0}/assertpy2/behave_matchers.py +0 -0
  100. {assertpy2-2.11.0 → assertpy2-2.13.0}/assertpy2/bytes_mixin.py +0 -0
  101. {assertpy2-2.11.0 → assertpy2-2.13.0}/assertpy2/errors.py +0 -0
  102. {assertpy2-2.11.0 → assertpy2-2.13.0}/assertpy2/py.typed +0 -0
  103. {assertpy2-2.11.0 → assertpy2-2.13.0}/assertpy2/pytest_plugin.py +0 -0
  104. {assertpy2-2.11.0 → assertpy2-2.13.0}/docs/assets/diff-equal.png +0 -0
  105. {assertpy2-2.11.0 → assertpy2-2.13.0}/docs/assets/diff-equal.svg +0 -0
  106. {assertpy2-2.11.0 → assertpy2-2.13.0}/docs/assets/diff-gallery.png +0 -0
  107. {assertpy2-2.11.0 → assertpy2-2.13.0}/docs/assets/diff-match.svg +0 -0
  108. {assertpy2-2.11.0 → assertpy2-2.13.0}/docs/assets/diff-sequence.svg +0 -0
  109. {assertpy2-2.11.0 → assertpy2-2.13.0}/docs/assets/diff-set.svg +0 -0
  110. {assertpy2-2.11.0 → assertpy2-2.13.0}/docs/data.md +0 -0
  111. {assertpy2-2.11.0 → assertpy2-2.13.0}/docs/logo-dark.svg +0 -0
  112. {assertpy2-2.11.0 → assertpy2-2.13.0}/docs/logo.svg +0 -0
  113. {assertpy2-2.11.0 → assertpy2-2.13.0}/docs/type-safety.md +0 -0
  114. {assertpy2-2.11.0 → assertpy2-2.13.0}/tests/test_behave_matchers.py +0 -0
  115. {assertpy2-2.11.0 → assertpy2-2.13.0}/tests/test_bool.py +0 -0
  116. {assertpy2-2.11.0 → assertpy2-2.13.0}/tests/test_callable.py +0 -0
  117. {assertpy2-2.11.0 → assertpy2-2.13.0}/tests/test_class.py +0 -0
  118. {assertpy2-2.11.0 → assertpy2-2.13.0}/tests/test_collection.py +0 -0
  119. {assertpy2-2.11.0 → assertpy2-2.13.0}/tests/test_core.py +0 -0
  120. {assertpy2-2.11.0 → assertpy2-2.13.0}/tests/test_custom_dict.py +0 -0
  121. {assertpy2-2.11.0 → assertpy2-2.13.0}/tests/test_custom_list.py +0 -0
  122. {assertpy2-2.11.0 → assertpy2-2.13.0}/tests/test_description.py +0 -0
  123. {assertpy2-2.11.0 → assertpy2-2.13.0}/tests/test_dict.py +0 -0
  124. {assertpy2-2.11.0 → assertpy2-2.13.0}/tests/test_errors.py +0 -0
  125. {assertpy2-2.11.0 → assertpy2-2.13.0}/tests/test_expected_exception.py +0 -0
  126. {assertpy2-2.11.0 → assertpy2-2.13.0}/tests/test_expected_warning.py +0 -0
  127. {assertpy2-2.11.0 → assertpy2-2.13.0}/tests/test_extensions.py +0 -0
  128. {assertpy2-2.11.0 → assertpy2-2.13.0}/tests/test_fail.py +0 -0
  129. {assertpy2-2.11.0 → assertpy2-2.13.0}/tests/test_file.py +0 -0
  130. {assertpy2-2.11.0 → assertpy2-2.13.0}/tests/test_grouped_soft.py +0 -0
  131. {assertpy2-2.11.0 → assertpy2-2.13.0}/tests/test_in.py +0 -0
  132. {assertpy2-2.11.0 → assertpy2-2.13.0}/tests/test_json.py +0 -0
  133. {assertpy2-2.11.0 → assertpy2-2.13.0}/tests/test_list.py +0 -0
  134. {assertpy2-2.11.0 → assertpy2-2.13.0}/tests/test_matcher_registry.py +0 -0
  135. {assertpy2-2.11.0 → assertpy2-2.13.0}/tests/test_namedtuple.py +0 -0
  136. {assertpy2-2.11.0 → assertpy2-2.13.0}/tests/test_none.py +0 -0
  137. {assertpy2-2.11.0 → assertpy2-2.13.0}/tests/test_not.py +0 -0
  138. {assertpy2-2.11.0 → assertpy2-2.13.0}/tests/test_numbers.py +0 -0
  139. {assertpy2-2.11.0 → assertpy2-2.13.0}/tests/test_overloads.py +0 -0
  140. {assertpy2-2.11.0 → assertpy2-2.13.0}/tests/test_pipeline.py +0 -0
  141. {assertpy2-2.11.0 → assertpy2-2.13.0}/tests/test_property_based.py +0 -0
  142. {assertpy2-2.11.0 → assertpy2-2.13.0}/tests/test_pytest_plugin.py +0 -0
  143. {assertpy2-2.11.0 → assertpy2-2.13.0}/tests/test_readme.py +0 -0
  144. {assertpy2-2.11.0 → assertpy2-2.13.0}/tests/test_recursive_compare.py +0 -0
  145. {assertpy2-2.11.0 → assertpy2-2.13.0}/tests/test_recursive_compare_attrs.py +0 -0
  146. {assertpy2-2.11.0 → assertpy2-2.13.0}/tests/test_recursive_compare_pydantic.py +0 -0
  147. {assertpy2-2.11.0 → assertpy2-2.13.0}/tests/test_regex_groups.py +0 -0
  148. {assertpy2-2.11.0 → assertpy2-2.13.0}/tests/test_rich_diff.py +0 -0
  149. {assertpy2-2.11.0 → assertpy2-2.13.0}/tests/test_same_as.py +0 -0
  150. {assertpy2-2.11.0 → assertpy2-2.13.0}/tests/test_satisfy.py +0 -0
  151. {assertpy2-2.11.0 → assertpy2-2.13.0}/tests/test_soft.py +0 -0
  152. {assertpy2-2.11.0 → assertpy2-2.13.0}/tests/test_soft_fail.py +0 -0
  153. {assertpy2-2.11.0 → assertpy2-2.13.0}/tests/test_string.py +0 -0
  154. {assertpy2-2.11.0 → assertpy2-2.13.0}/tests/test_traceback.py +0 -0
  155. {assertpy2-2.11.0 → assertpy2-2.13.0}/tests/test_type.py +0 -0
  156. {assertpy2-2.11.0 → assertpy2-2.13.0}/tests/test_warn.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
 
@@ -19,9 +19,9 @@ 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
- - 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,12 +29,12 @@ 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
 
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:
@@ -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.11.0
3
+ Version: 2.13.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
@@ -135,7 +135,7 @@ assert_that(response).is_equal_to(expected)
135
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
136
  </p>
137
137
 
138
- Recursive diffs work for dicts, dataclasses, namedtuples, attrs, and Pydantic models.
138
+ Recursive diffs work for dicts, dataclasses, namedtuples, and Pydantic models.
139
139
  For responses with dynamic fields (IDs, timestamps), validate a subset with
140
140
  [`matches_structure()`](https://solganis.github.io/assertpy2/matchers/#structural-matching) instead of exact equality.
141
141
 
@@ -160,16 +160,16 @@ Your IDE shows only methods relevant to the value you're testing, not all 100+:
160
160
 
161
161
  See the [**Type Safety**](https://solganis.github.io/assertpy2/type-safety/) guide for the full walkthrough.
162
162
 
163
- ---
164
-
165
163
  <h2 align="center">Features</h2>
166
164
 
167
165
  **Fluent API**
168
166
 
169
167
  - [**Composable matchers**](https://solganis.github.io/assertpy2/matchers/): `match.greater_than(5)`, `match.is_uuid()`, combine with `&`, `|`, `~`. Also work with plain `assert ==`.
170
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.
171
170
  - [**Universal negation**](https://solganis.github.io/assertpy2/fluent/#universal-negation): `.not_` inverts any assertion without dedicated `is_not_*` methods.
172
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()`.
173
173
  - [**Fluent chaining**](https://solganis.github.io/assertpy2/fluent/#chaining): write assertions as readable one-liners that chain naturally.
174
174
 
175
175
  **Built-in types**
@@ -177,7 +177,8 @@ See the [**Type Safety**](https://solganis.github.io/assertpy2/type-safety/) gui
177
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).
178
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`.
179
179
  - [**Dynamic assertions**](https://solganis.github.io/assertpy2/assertions/#dynamic-assertions-on-objects): `has_<name>()` for any attribute, property, or zero-argument method.
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).
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.
181
182
  - [**Extracting**](https://solganis.github.io/assertpy2/assertions/#extracting-attributes-from-objects): flatten collections on attributes with `filter` and `sort` support.
182
183
 
183
184
  **Testing**
@@ -202,8 +203,6 @@ See the [**Type Safety**](https://solganis.github.io/assertpy2/type-safety/) gui
202
203
 
203
204
  See the [full documentation](https://solganis.github.io/assertpy2/) for all assertion methods, examples, and advanced features.
204
205
 
205
- ---
206
-
207
206
  <h2 align="center"><a href="https://solganis.github.io/assertpy2/integrations/">Integrations</a></h2>
208
207
 
209
208
  Optional adapters, each its own extra; full configuration and examples are in the
@@ -87,7 +87,7 @@ assert_that(response).is_equal_to(expected)
87
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
88
  </p>
89
89
 
90
- Recursive diffs work for dicts, dataclasses, namedtuples, attrs, and Pydantic models.
90
+ Recursive diffs work for dicts, dataclasses, namedtuples, and Pydantic models.
91
91
  For responses with dynamic fields (IDs, timestamps), validate a subset with
92
92
  [`matches_structure()`](https://solganis.github.io/assertpy2/matchers/#structural-matching) instead of exact equality.
93
93
 
@@ -112,16 +112,16 @@ Your IDE shows only methods relevant to the value you're testing, not all 100+:
112
112
 
113
113
  See the [**Type Safety**](https://solganis.github.io/assertpy2/type-safety/) guide for the full walkthrough.
114
114
 
115
- ---
116
-
117
115
  <h2 align="center">Features</h2>
118
116
 
119
117
  **Fluent API**
120
118
 
121
119
  - [**Composable matchers**](https://solganis.github.io/assertpy2/matchers/): `match.greater_than(5)`, `match.is_uuid()`, combine with `&`, `|`, `~`. Also work with plain `assert ==`.
122
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.
123
122
  - [**Universal negation**](https://solganis.github.io/assertpy2/fluent/#universal-negation): `.not_` inverts any assertion without dedicated `is_not_*` methods.
124
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()`.
125
125
  - [**Fluent chaining**](https://solganis.github.io/assertpy2/fluent/#chaining): write assertions as readable one-liners that chain naturally.
126
126
 
127
127
  **Built-in types**
@@ -129,7 +129,8 @@ See the [**Type Safety**](https://solganis.github.io/assertpy2/type-safety/) gui
129
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).
130
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`.
131
131
  - [**Dynamic assertions**](https://solganis.github.io/assertpy2/assertions/#dynamic-assertions-on-objects): `has_<name>()` for any attribute, property, or zero-argument method.
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).
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.
133
134
  - [**Extracting**](https://solganis.github.io/assertpy2/assertions/#extracting-attributes-from-objects): flatten collections on attributes with `filter` and `sort` support.
134
135
 
135
136
  **Testing**
@@ -154,8 +155,6 @@ See the [**Type Safety**](https://solganis.github.io/assertpy2/type-safety/) gui
154
155
 
155
156
  See the [full documentation](https://solganis.github.io/assertpy2/) for all assertion methods, examples, and advanced features.
156
157
 
157
- ---
158
-
159
158
  <h2 align="center"><a href="https://solganis.github.io/assertpy2/integrations/">Integrations</a></h2>
160
159
 
161
160
  Optional adapters, each its own extra; full configuration and examples are in the
@@ -16,6 +16,7 @@ from .async_assertions import AsyncAssertionBuilder
16
16
  from .errors import AssertionFailure, DiffEntry, DiffResult
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,7 @@ __all__ = [
24
25
  "DiffResult",
25
26
  "Matcher",
26
27
  "NegatedBuilder",
28
+ "SnapshotCreatedWarning",
27
29
  "SoftAssertionCollector",
28
30
  "WarningLoggingAdapter",
29
31
  "__version__",
@@ -0,0 +1,222 @@
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 dataclasses
17
+ import math
18
+ import numbers
19
+ import re
20
+ from dataclasses import dataclass
21
+ from typing import TYPE_CHECKING
22
+
23
+ from ._introspection import is_mapping_like, is_model_dump_object
24
+
25
+ if TYPE_CHECKING:
26
+ from collections.abc import Callable
27
+
28
+
29
+ @dataclass(frozen=True, slots=True, kw_only=True)
30
+ class _CompareConfig:
31
+ """Tolerance and custom comparators for a single ``is_equal_to`` call.
32
+
33
+ ``tolerance`` is an absolute tolerance applied to real-number leaves; ``comparators`` maps a ``type`` or
34
+ an immediate field name to a ``(actual, expected) -> bool`` predicate that owns matching leaves.
35
+ """
36
+
37
+ tolerance: float | None = None
38
+ comparators: dict[object, Callable[[object, object], bool]] | None = None
39
+
40
+
41
+ def _build_compare_config(tolerance, comparators) -> _CompareConfig | None:
42
+ """Validate the ``is_equal_to`` ``tolerance``/``comparators`` kwargs and build a config (``None`` if neither).
43
+
44
+ ``tolerance`` must be a non-negative real number (not ``bool``/``complex``/``NaN``); ``comparators`` must be a
45
+ dict of ``(actual, expected) -> bool`` callables keyed by ``type`` or field name.
46
+ """
47
+ if tolerance is None and comparators is None:
48
+ return None
49
+ if tolerance is not None:
50
+ if isinstance(tolerance, bool) or not isinstance(tolerance, numbers.Number) or isinstance(tolerance, complex):
51
+ raise TypeError("given tolerance arg must be a real number")
52
+ if math.isnan(tolerance):
53
+ raise ValueError("given tolerance arg must not be NaN")
54
+ if tolerance < 0:
55
+ raise ValueError("given tolerance arg must not be negative")
56
+ if comparators is not None:
57
+ if not isinstance(comparators, dict):
58
+ raise TypeError("given comparators arg must be a dict")
59
+ for comparator in comparators.values():
60
+ if not callable(comparator):
61
+ raise TypeError("each comparator must be callable")
62
+ return _CompareConfig(tolerance=tolerance, comparators=comparators)
63
+
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) -> 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("is_equal_to", operand) from error
146
+
147
+
148
+ def _is_real_number(value) -> bool:
149
+ """Return whether ``value`` is a real number eligible for tolerance (excludes ``bool`` and ``complex``).
150
+
151
+ Array/frame-likes are not `numbers.Number`, so they are excluded too - tolerance never triggers
152
+ their element-wise ``==`` that has no single truth value.
153
+ """
154
+ return isinstance(value, numbers.Number) and not isinstance(value, (bool, complex))
155
+
156
+
157
+ def _within_tolerance(actual, expected, tolerance) -> bool:
158
+ """Return whether two real numbers are within ``tolerance`` (absolute); ``NaN`` is never within.
159
+
160
+ Only actual ``float`` operands are checked for ``NaN`` (other reals cannot be ``NaN``), which also keeps
161
+ ``math.isnan`` off arbitrary-precision ``int`` values that would overflow it.
162
+ """
163
+ if isinstance(actual, float) and math.isnan(actual):
164
+ return False
165
+ if isinstance(expected, float) and math.isnan(expected):
166
+ return False
167
+ return abs(actual - expected) <= tolerance
168
+
169
+
170
+ def _resolve_comparator(actual, config: _CompareConfig, *, field):
171
+ """Resolve the comparator owning a leaf: immediate field name first, then exact type, then ``isinstance``.
172
+
173
+ ``field`` is the leaf's immediate key/field name (``None`` for sequence elements and bare scalars, which
174
+ have no name). Returns ``None`` when no comparator applies, so the caller falls back to tolerance / ``==``.
175
+ """
176
+ comparators = config.comparators
177
+ if comparators is None:
178
+ return None
179
+ if field is not None and not isinstance(field, type) and field in comparators:
180
+ return comparators[field]
181
+ if type(actual) in comparators:
182
+ return comparators[type(actual)]
183
+ for key, comparator in comparators.items():
184
+ if isinstance(key, type) and isinstance(actual, key):
185
+ return comparator
186
+ return None
187
+
188
+
189
+ def _node_decision(actual, expected, config: _CompareConfig | None, *, field=None) -> str:
190
+ """Classify a node as ``"equal"``, ``"leaf"`` or ``"recurse"``.
191
+
192
+ With ``config is None`` this is exactly the engine's historical behavior: differing values ``"recurse"``
193
+ (to decompose into a sub-diff), equal values are ``"equal"`` (skipped); ``"leaf"`` never occurs. With a
194
+ config, a matching comparator or tolerance owns the node - it is classified ``"equal"`` or ``"leaf"`` and
195
+ never recursed into.
196
+ """
197
+ if config is not None:
198
+ comparator = _resolve_comparator(actual, config, field=field)
199
+ if comparator is not None:
200
+ return "equal" if comparator(actual, expected) else "leaf"
201
+ if config.tolerance is not None and _is_real_number(actual) and _is_real_number(expected):
202
+ return "equal" if _within_tolerance(actual, expected, config.tolerance) else "leaf"
203
+ return "recurse" if _guarded_not_equal(actual, expected) else "equal"
204
+
205
+
206
+ def _spec_matches(key, value, specs) -> bool:
207
+ """Return whether a dict ``key``/``value`` matches any ignore/include ``spec``.
208
+
209
+ A spec matches by exact key equality (today's behavior), by a compiled `re.Pattern` searched
210
+ against ``str(key)``, or by a ``type`` the ``value`` is an instance of. Nested-path tuples never match
211
+ here - they are expanded by the recursion in `HelpersMixin._dict_not_equal()`.
212
+ """
213
+ for spec in specs:
214
+ if isinstance(spec, re.Pattern):
215
+ if spec.search(str(key)):
216
+ return True
217
+ elif isinstance(spec, type):
218
+ if isinstance(value, spec):
219
+ return True
220
+ elif spec == key:
221
+ return True
222
+ 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,6 +5,7 @@ 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
10
  from .errors import DiffEntry, DiffResult
10
11
 
@@ -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,6 +93,7 @@ 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
@@ -98,7 +101,12 @@ class _MixinBase:
98
101
 
99
102
  @staticmethod
100
103
  def _sub_diff_entries(
101
- actual: object, expected: object, prefix: str, *, _seen: set[int] | None = ...
104
+ actual: object,
105
+ expected: object,
106
+ prefix: str,
107
+ *,
108
+ _seen: set[int] | None = ...,
109
+ config: _CompareConfig | None = ...,
102
110
  ) -> list[DiffEntry] | None: ...
103
111
 
104
112
  # NumericMixin class attrs used by HelpersMixin._validate_between_args