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.
- assertpy2-2.14.0/.github/dependabot.yml +31 -0
- {assertpy2-2.12.0 → assertpy2-2.14.0}/.github/workflows/ci.yml +22 -4
- {assertpy2-2.12.0 → assertpy2-2.14.0}/.github/workflows/codeql.yml +1 -1
- {assertpy2-2.12.0 → assertpy2-2.14.0}/.github/workflows/docs.yml +2 -2
- {assertpy2-2.12.0 → assertpy2-2.14.0}/.github/workflows/mutation.yml +2 -2
- {assertpy2-2.12.0 → assertpy2-2.14.0}/.github/workflows/publish.yml +2 -2
- {assertpy2-2.12.0 → assertpy2-2.14.0}/.github/workflows/scorecard.yml +1 -1
- {assertpy2-2.12.0 → assertpy2-2.14.0}/.github/workflows/zizmor.yml +2 -2
- {assertpy2-2.12.0 → assertpy2-2.14.0}/PKG-INFO +2 -3
- {assertpy2-2.12.0 → assertpy2-2.14.0}/README.md +0 -1
- {assertpy2-2.12.0 → assertpy2-2.14.0}/assertpy2/__init__.py +5 -1
- {assertpy2-2.12.0 → assertpy2-2.14.0}/assertpy2/_compare.py +99 -2
- assertpy2-2.14.0/assertpy2/_diff.py +346 -0
- {assertpy2-2.12.0 → assertpy2-2.14.0}/assertpy2/_introspection.py +14 -1
- {assertpy2-2.12.0 → assertpy2-2.14.0}/assertpy2/_mixin_base.py +1 -11
- {assertpy2-2.12.0 → assertpy2-2.14.0}/assertpy2/_typing.py +34 -2
- {assertpy2-2.12.0 → assertpy2-2.14.0}/assertpy2/assertpy.py +69 -16
- assertpy2-2.14.0/assertpy2/async_assertions.py +208 -0
- {assertpy2-2.12.0 → assertpy2-2.14.0}/assertpy2/base.py +31 -378
- {assertpy2-2.12.0 → assertpy2-2.14.0}/assertpy2/dataframe.py +9 -4
- assertpy2-2.14.0/assertpy2/errors.py +166 -0
- {assertpy2-2.12.0 → assertpy2-2.14.0}/assertpy2/helpers.py +13 -12
- {assertpy2-2.12.0 → assertpy2-2.14.0}/assertpy2/matchers.py +18 -7
- {assertpy2-2.12.0 → assertpy2-2.14.0}/assertpy2/pytest_plugin.py +76 -11
- {assertpy2-2.12.0 → assertpy2-2.14.0}/assertpy2/snapshot.py +69 -5
- {assertpy2-2.12.0 → assertpy2-2.14.0}/docs/assertions.md +5 -1
- {assertpy2-2.12.0 → assertpy2-2.14.0}/docs/errors.md +32 -0
- {assertpy2-2.12.0 → assertpy2-2.14.0}/docs/integrations.md +38 -5
- {assertpy2-2.12.0 → assertpy2-2.14.0}/docs/matchers.md +11 -1
- {assertpy2-2.12.0 → assertpy2-2.14.0}/docs/reference/errors.md +12 -0
- assertpy2-2.14.0/docs/reference/snapshots.md +11 -0
- assertpy2-2.14.0/docs/testing.md +193 -0
- {assertpy2-2.12.0 → assertpy2-2.14.0}/mkdocs.yml +1 -1
- assertpy2-2.14.0/pyproject.toml +158 -0
- assertpy2-2.14.0/tests/test_async.py +482 -0
- {assertpy2-2.12.0 → assertpy2-2.14.0}/tests/test_dataframe.py +21 -4
- {assertpy2-2.12.0 → assertpy2-2.14.0}/tests/test_dict_compare.py +21 -0
- assertpy2-2.14.0/tests/test_equals.py +276 -0
- {assertpy2-2.12.0 → assertpy2-2.14.0}/tests/test_errors.py +49 -0
- {assertpy2-2.12.0 → assertpy2-2.14.0}/tests/test_extensions.py +65 -0
- {assertpy2-2.12.0 → assertpy2-2.14.0}/tests/test_matchers.py +24 -0
- {assertpy2-2.12.0 → assertpy2-2.14.0}/tests/test_not.py +5 -0
- assertpy2-2.14.0/tests/test_protocol_parity.py +86 -0
- {assertpy2-2.12.0 → assertpy2-2.14.0}/tests/test_pytest_plugin.py +170 -14
- {assertpy2-2.12.0 → assertpy2-2.14.0}/tests/test_recursive_assertion.py +20 -20
- {assertpy2-2.12.0 → assertpy2-2.14.0}/tests/test_rich_diff.py +78 -78
- {assertpy2-2.12.0 → assertpy2-2.14.0}/tests/test_snapshots.py +96 -1
- {assertpy2-2.12.0 → assertpy2-2.14.0}/tests/test_structural.py +56 -0
- {assertpy2-2.12.0 → assertpy2-2.14.0}/tests/test_typing.py +26 -0
- {assertpy2-2.12.0 → assertpy2-2.14.0}/tests/test_warn.py +39 -23
- {assertpy2-2.12.0 → assertpy2-2.14.0}/uv.lock +237 -69
- assertpy2-2.12.0/.github/dependabot.yml +0 -20
- assertpy2-2.12.0/assertpy2/async_assertions.py +0 -82
- assertpy2-2.12.0/assertpy2/errors.py +0 -51
- assertpy2-2.12.0/docs/reference/snapshots.md +0 -5
- assertpy2-2.12.0/docs/testing.md +0 -119
- assertpy2-2.12.0/pyproject.toml +0 -104
- assertpy2-2.12.0/tests/test_async.py +0 -164
- assertpy2-2.12.0/tests/test_equals.py +0 -138
- {assertpy2-2.12.0 → assertpy2-2.14.0}/.codecov.yml +0 -0
- {assertpy2-2.12.0 → assertpy2-2.14.0}/.gitattributes +0 -0
- {assertpy2-2.12.0 → assertpy2-2.14.0}/.gitignore +0 -0
- {assertpy2-2.12.0 → assertpy2-2.14.0}/CONTRIBUTING.md +0 -0
- {assertpy2-2.12.0 → assertpy2-2.14.0}/LICENSE +0 -0
- {assertpy2-2.12.0 → assertpy2-2.14.0}/SECURITY.md +0 -0
- {assertpy2-2.12.0 → assertpy2-2.14.0}/assertpy2/_compat.py +0 -0
- {assertpy2-2.12.0 → assertpy2-2.14.0}/assertpy2/behave_matchers.py +0 -0
- {assertpy2-2.12.0 → assertpy2-2.14.0}/assertpy2/bytes_mixin.py +0 -0
- {assertpy2-2.12.0 → assertpy2-2.14.0}/assertpy2/collection.py +0 -0
- {assertpy2-2.12.0 → assertpy2-2.14.0}/assertpy2/contains.py +0 -0
- {assertpy2-2.12.0 → assertpy2-2.14.0}/assertpy2/date.py +0 -0
- {assertpy2-2.12.0 → assertpy2-2.14.0}/assertpy2/dict.py +0 -0
- {assertpy2-2.12.0 → assertpy2-2.14.0}/assertpy2/dynamic.py +0 -0
- {assertpy2-2.12.0 → assertpy2-2.14.0}/assertpy2/exception.py +0 -0
- {assertpy2-2.12.0 → assertpy2-2.14.0}/assertpy2/extracting.py +0 -0
- {assertpy2-2.12.0 → assertpy2-2.14.0}/assertpy2/file.py +0 -0
- {assertpy2-2.12.0 → assertpy2-2.14.0}/assertpy2/json_mixin.py +0 -0
- {assertpy2-2.12.0 → assertpy2-2.14.0}/assertpy2/numeric.py +0 -0
- {assertpy2-2.12.0 → assertpy2-2.14.0}/assertpy2/py.typed +0 -0
- {assertpy2-2.12.0 → assertpy2-2.14.0}/assertpy2/string.py +0 -0
- {assertpy2-2.12.0 → assertpy2-2.14.0}/assertpy2/warning.py +0 -0
- {assertpy2-2.12.0 → assertpy2-2.14.0}/docs/assets/diff-equal.png +0 -0
- {assertpy2-2.12.0 → assertpy2-2.14.0}/docs/assets/diff-equal.svg +0 -0
- {assertpy2-2.12.0 → assertpy2-2.14.0}/docs/assets/diff-gallery.png +0 -0
- {assertpy2-2.12.0 → assertpy2-2.14.0}/docs/assets/diff-match.svg +0 -0
- {assertpy2-2.12.0 → assertpy2-2.14.0}/docs/assets/diff-sequence.svg +0 -0
- {assertpy2-2.12.0 → assertpy2-2.14.0}/docs/assets/diff-set.svg +0 -0
- {assertpy2-2.12.0 → assertpy2-2.14.0}/docs/comparison.md +0 -0
- {assertpy2-2.12.0 → assertpy2-2.14.0}/docs/data.md +0 -0
- {assertpy2-2.12.0 → assertpy2-2.14.0}/docs/extending.md +0 -0
- {assertpy2-2.12.0 → assertpy2-2.14.0}/docs/fluent.md +0 -0
- {assertpy2-2.12.0 → assertpy2-2.14.0}/docs/getting-started.md +0 -0
- {assertpy2-2.12.0 → assertpy2-2.14.0}/docs/index.md +0 -0
- {assertpy2-2.12.0 → assertpy2-2.14.0}/docs/logo-dark.svg +0 -0
- {assertpy2-2.12.0 → assertpy2-2.14.0}/docs/logo.svg +0 -0
- {assertpy2-2.12.0 → assertpy2-2.14.0}/docs/migration.md +0 -0
- {assertpy2-2.12.0 → assertpy2-2.14.0}/docs/reference/async.md +0 -0
- {assertpy2-2.12.0 → assertpy2-2.14.0}/docs/reference/bytes.md +0 -0
- {assertpy2-2.12.0 → assertpy2-2.14.0}/docs/reference/collections.md +0 -0
- {assertpy2-2.12.0 → assertpy2-2.14.0}/docs/reference/containment.md +0 -0
- {assertpy2-2.12.0 → assertpy2-2.14.0}/docs/reference/core.md +0 -0
- {assertpy2-2.12.0 → assertpy2-2.14.0}/docs/reference/dataframes.md +0 -0
- {assertpy2-2.12.0 → assertpy2-2.14.0}/docs/reference/dates.md +0 -0
- {assertpy2-2.12.0 → assertpy2-2.14.0}/docs/reference/dicts.md +0 -0
- {assertpy2-2.12.0 → assertpy2-2.14.0}/docs/reference/dynamic.md +0 -0
- {assertpy2-2.12.0 → assertpy2-2.14.0}/docs/reference/entry-points.md +0 -0
- {assertpy2-2.12.0 → assertpy2-2.14.0}/docs/reference/exceptions.md +0 -0
- {assertpy2-2.12.0 → assertpy2-2.14.0}/docs/reference/extracting.md +0 -0
- {assertpy2-2.12.0 → assertpy2-2.14.0}/docs/reference/files.md +0 -0
- {assertpy2-2.12.0 → assertpy2-2.14.0}/docs/reference/json.md +0 -0
- {assertpy2-2.12.0 → assertpy2-2.14.0}/docs/reference/matchers.md +0 -0
- {assertpy2-2.12.0 → assertpy2-2.14.0}/docs/reference/numbers.md +0 -0
- {assertpy2-2.12.0 → assertpy2-2.14.0}/docs/reference/overview.md +0 -0
- {assertpy2-2.12.0 → assertpy2-2.14.0}/docs/reference/strings.md +0 -0
- {assertpy2-2.12.0 → assertpy2-2.14.0}/docs/reference/warnings.md +0 -0
- {assertpy2-2.12.0 → assertpy2-2.14.0}/docs/stylesheets/extra.css +0 -0
- {assertpy2-2.12.0 → assertpy2-2.14.0}/docs/type-safety.md +0 -0
- {assertpy2-2.12.0 → assertpy2-2.14.0}/tests/test_behave_matchers.py +0 -0
- {assertpy2-2.12.0 → assertpy2-2.14.0}/tests/test_bool.py +0 -0
- {assertpy2-2.12.0 → assertpy2-2.14.0}/tests/test_bytes.py +0 -0
- {assertpy2-2.12.0 → assertpy2-2.14.0}/tests/test_callable.py +0 -0
- {assertpy2-2.12.0 → assertpy2-2.14.0}/tests/test_chaining.py +0 -0
- {assertpy2-2.12.0 → assertpy2-2.14.0}/tests/test_class.py +0 -0
- {assertpy2-2.12.0 → assertpy2-2.14.0}/tests/test_collection.py +0 -0
- {assertpy2-2.12.0 → assertpy2-2.14.0}/tests/test_core.py +0 -0
- {assertpy2-2.12.0 → assertpy2-2.14.0}/tests/test_custom_dict.py +0 -0
- {assertpy2-2.12.0 → assertpy2-2.14.0}/tests/test_custom_list.py +0 -0
- {assertpy2-2.12.0 → assertpy2-2.14.0}/tests/test_datetime.py +0 -0
- {assertpy2-2.12.0 → assertpy2-2.14.0}/tests/test_description.py +0 -0
- {assertpy2-2.12.0 → assertpy2-2.14.0}/tests/test_dict.py +0 -0
- {assertpy2-2.12.0 → assertpy2-2.14.0}/tests/test_dynamic.py +0 -0
- {assertpy2-2.12.0 → assertpy2-2.14.0}/tests/test_expected_exception.py +0 -0
- {assertpy2-2.12.0 → assertpy2-2.14.0}/tests/test_expected_warning.py +0 -0
- {assertpy2-2.12.0 → assertpy2-2.14.0}/tests/test_extracting.py +0 -0
- {assertpy2-2.12.0 → assertpy2-2.14.0}/tests/test_fail.py +0 -0
- {assertpy2-2.12.0 → assertpy2-2.14.0}/tests/test_file.py +0 -0
- {assertpy2-2.12.0 → assertpy2-2.14.0}/tests/test_grouped_soft.py +0 -0
- {assertpy2-2.12.0 → assertpy2-2.14.0}/tests/test_in.py +0 -0
- {assertpy2-2.12.0 → assertpy2-2.14.0}/tests/test_iterable_cluster.py +0 -0
- {assertpy2-2.12.0 → assertpy2-2.14.0}/tests/test_json.py +0 -0
- {assertpy2-2.12.0 → assertpy2-2.14.0}/tests/test_list.py +0 -0
- {assertpy2-2.12.0 → assertpy2-2.14.0}/tests/test_matcher_registry.py +0 -0
- {assertpy2-2.12.0 → assertpy2-2.14.0}/tests/test_mutation_hardening.py +0 -0
- {assertpy2-2.12.0 → assertpy2-2.14.0}/tests/test_namedtuple.py +0 -0
- {assertpy2-2.12.0 → assertpy2-2.14.0}/tests/test_none.py +0 -0
- {assertpy2-2.12.0 → assertpy2-2.14.0}/tests/test_numbers.py +0 -0
- {assertpy2-2.12.0 → assertpy2-2.14.0}/tests/test_overloads.py +0 -0
- {assertpy2-2.12.0 → assertpy2-2.14.0}/tests/test_pipeline.py +0 -0
- {assertpy2-2.12.0 → assertpy2-2.14.0}/tests/test_property_based.py +0 -0
- {assertpy2-2.12.0 → assertpy2-2.14.0}/tests/test_readme.py +0 -0
- {assertpy2-2.12.0 → assertpy2-2.14.0}/tests/test_recursive_compare.py +0 -0
- {assertpy2-2.12.0 → assertpy2-2.14.0}/tests/test_recursive_compare_attrs.py +0 -0
- {assertpy2-2.12.0 → assertpy2-2.14.0}/tests/test_recursive_compare_config.py +0 -0
- {assertpy2-2.12.0 → assertpy2-2.14.0}/tests/test_recursive_compare_pydantic.py +0 -0
- {assertpy2-2.12.0 → assertpy2-2.14.0}/tests/test_regex_groups.py +0 -0
- {assertpy2-2.12.0 → assertpy2-2.14.0}/tests/test_same_as.py +0 -0
- {assertpy2-2.12.0 → assertpy2-2.14.0}/tests/test_satisfy.py +0 -0
- {assertpy2-2.12.0 → assertpy2-2.14.0}/tests/test_soft.py +0 -0
- {assertpy2-2.12.0 → assertpy2-2.14.0}/tests/test_soft_fail.py +0 -0
- {assertpy2-2.12.0 → assertpy2-2.14.0}/tests/test_string.py +0 -0
- {assertpy2-2.12.0 → assertpy2-2.14.0}/tests/test_traceback.py +0 -0
- {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@
|
|
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@
|
|
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@
|
|
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@
|
|
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@
|
|
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@
|
|
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@
|
|
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@
|
|
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@
|
|
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@
|
|
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@
|
|
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@
|
|
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@
|
|
22
|
+
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
|
23
23
|
with:
|
|
24
24
|
persist-credentials: false
|
|
25
|
-
- uses: zizmorcore/zizmor-action@
|
|
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.
|
|
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
|
-
(`
|
|
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
|
|
214
|
+
return "recurse" if _guarded_not_equal(actual, expected) else "equal"
|
|
118
215
|
|
|
119
216
|
|
|
120
217
|
def _spec_matches(key, value, specs) -> bool:
|