assertpy2 2.1.1__tar.gz → 2.1.3__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.1.3/.github/dependabot.yml +15 -0
- {assertpy2-2.1.1 → assertpy2-2.1.3}/.github/workflows/ci.yml +10 -7
- assertpy2-2.1.3/.github/workflows/codeql.yml +24 -0
- assertpy2-2.1.3/.github/workflows/publish.yml +44 -0
- assertpy2-2.1.3/.github/workflows/scorecard.yml +34 -0
- {assertpy2-2.1.1 → assertpy2-2.1.3}/PKG-INFO +24 -19
- {assertpy2-2.1.1 → assertpy2-2.1.3}/README.md +23 -18
- assertpy2-2.1.3/SECURITY.md +16 -0
- {assertpy2-2.1.1 → assertpy2-2.1.3}/assertpy2/assertpy.py +1 -1
- {assertpy2-2.1.1 → assertpy2-2.1.3}/pyproject.toml +1 -1
- {assertpy2-2.1.1 → assertpy2-2.1.3}/tests/test_matchers.py +0 -2
- {assertpy2-2.1.1 → assertpy2-2.1.3}/tests/test_overloads.py +0 -2
- assertpy2-2.1.1/.github/workflows/publish.yml +0 -29
- assertpy2-2.1.1/CLAUDE.md +0 -113
- {assertpy2-2.1.1 → assertpy2-2.1.3}/.codecov.yml +0 -0
- {assertpy2-2.1.1 → assertpy2-2.1.3}/.gitignore +0 -0
- {assertpy2-2.1.1 → assertpy2-2.1.3}/CONTRIBUTING.md +0 -0
- {assertpy2-2.1.1 → assertpy2-2.1.3}/LICENSE +0 -0
- {assertpy2-2.1.1 → assertpy2-2.1.3}/assertpy2/__init__.py +0 -0
- {assertpy2-2.1.1 → assertpy2-2.1.3}/assertpy2/_typing.py +0 -0
- {assertpy2-2.1.1 → assertpy2-2.1.3}/assertpy2/async_assertions.py +0 -0
- {assertpy2-2.1.1 → assertpy2-2.1.3}/assertpy2/base.py +0 -0
- {assertpy2-2.1.1 → assertpy2-2.1.3}/assertpy2/collection.py +0 -0
- {assertpy2-2.1.1 → assertpy2-2.1.3}/assertpy2/contains.py +0 -0
- {assertpy2-2.1.1 → assertpy2-2.1.3}/assertpy2/date.py +0 -0
- {assertpy2-2.1.1 → assertpy2-2.1.3}/assertpy2/dict.py +0 -0
- {assertpy2-2.1.1 → assertpy2-2.1.3}/assertpy2/dynamic.py +0 -0
- {assertpy2-2.1.1 → assertpy2-2.1.3}/assertpy2/errors.py +0 -0
- {assertpy2-2.1.1 → assertpy2-2.1.3}/assertpy2/exception.py +0 -0
- {assertpy2-2.1.1 → assertpy2-2.1.3}/assertpy2/extracting.py +0 -0
- {assertpy2-2.1.1 → assertpy2-2.1.3}/assertpy2/file.py +0 -0
- {assertpy2-2.1.1 → assertpy2-2.1.3}/assertpy2/helpers.py +0 -0
- {assertpy2-2.1.1 → assertpy2-2.1.3}/assertpy2/matchers.py +0 -0
- {assertpy2-2.1.1 → assertpy2-2.1.3}/assertpy2/numeric.py +0 -0
- {assertpy2-2.1.1 → assertpy2-2.1.3}/assertpy2/py.typed +0 -0
- {assertpy2-2.1.1 → assertpy2-2.1.3}/assertpy2/pytest_plugin.py +0 -0
- {assertpy2-2.1.1 → assertpy2-2.1.3}/assertpy2/snapshot.py +0 -0
- {assertpy2-2.1.1 → assertpy2-2.1.3}/assertpy2/string.py +0 -0
- {assertpy2-2.1.1 → assertpy2-2.1.3}/docs/api.md +0 -0
- {assertpy2-2.1.1 → assertpy2-2.1.3}/docs/logo-dark.svg +0 -0
- {assertpy2-2.1.1 → assertpy2-2.1.3}/docs/logo.svg +0 -0
- {assertpy2-2.1.1 → assertpy2-2.1.3}/tests/test_async.py +0 -0
- {assertpy2-2.1.1 → assertpy2-2.1.3}/tests/test_bool.py +0 -0
- {assertpy2-2.1.1 → assertpy2-2.1.3}/tests/test_class.py +0 -0
- {assertpy2-2.1.1 → assertpy2-2.1.3}/tests/test_collection.py +0 -0
- {assertpy2-2.1.1 → assertpy2-2.1.3}/tests/test_core.py +0 -0
- {assertpy2-2.1.1 → assertpy2-2.1.3}/tests/test_custom_dict.py +0 -0
- {assertpy2-2.1.1 → assertpy2-2.1.3}/tests/test_custom_list.py +0 -0
- {assertpy2-2.1.1 → assertpy2-2.1.3}/tests/test_datetime.py +0 -0
- {assertpy2-2.1.1 → assertpy2-2.1.3}/tests/test_description.py +0 -0
- {assertpy2-2.1.1 → assertpy2-2.1.3}/tests/test_dict.py +0 -0
- {assertpy2-2.1.1 → assertpy2-2.1.3}/tests/test_dict_compare.py +0 -0
- {assertpy2-2.1.1 → assertpy2-2.1.3}/tests/test_dyn.py +0 -0
- {assertpy2-2.1.1 → assertpy2-2.1.3}/tests/test_equals.py +0 -0
- {assertpy2-2.1.1 → assertpy2-2.1.3}/tests/test_errors.py +0 -0
- {assertpy2-2.1.1 → assertpy2-2.1.3}/tests/test_expected_exception.py +0 -0
- {assertpy2-2.1.1 → assertpy2-2.1.3}/tests/test_extensions.py +0 -0
- {assertpy2-2.1.1 → assertpy2-2.1.3}/tests/test_extracting.py +0 -0
- {assertpy2-2.1.1 → assertpy2-2.1.3}/tests/test_fail.py +0 -0
- {assertpy2-2.1.1 → assertpy2-2.1.3}/tests/test_file.py +0 -0
- {assertpy2-2.1.1 → assertpy2-2.1.3}/tests/test_in.py +0 -0
- {assertpy2-2.1.1 → assertpy2-2.1.3}/tests/test_list.py +0 -0
- {assertpy2-2.1.1 → assertpy2-2.1.3}/tests/test_namedtuple.py +0 -0
- {assertpy2-2.1.1 → assertpy2-2.1.3}/tests/test_none.py +0 -0
- {assertpy2-2.1.1 → assertpy2-2.1.3}/tests/test_numbers.py +0 -0
- {assertpy2-2.1.1 → assertpy2-2.1.3}/tests/test_pytest_plugin.py +0 -0
- {assertpy2-2.1.1 → assertpy2-2.1.3}/tests/test_readme.py +0 -0
- {assertpy2-2.1.1 → assertpy2-2.1.3}/tests/test_same_as.py +0 -0
- {assertpy2-2.1.1 → assertpy2-2.1.3}/tests/test_snapshots.py +0 -0
- {assertpy2-2.1.1 → assertpy2-2.1.3}/tests/test_soft.py +0 -0
- {assertpy2-2.1.1 → assertpy2-2.1.3}/tests/test_soft_fail.py +0 -0
- {assertpy2-2.1.1 → assertpy2-2.1.3}/tests/test_string.py +0 -0
- {assertpy2-2.1.1 → assertpy2-2.1.3}/tests/test_structural.py +0 -0
- {assertpy2-2.1.1 → assertpy2-2.1.3}/tests/test_traceback.py +0 -0
- {assertpy2-2.1.1 → assertpy2-2.1.3}/tests/test_type.py +0 -0
- {assertpy2-2.1.1 → assertpy2-2.1.3}/tests/test_warn.py +0 -0
- {assertpy2-2.1.1 → assertpy2-2.1.3}/uv.lock +0 -0
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
version: 2
|
|
2
|
+
updates:
|
|
3
|
+
- package-ecosystem: "pip"
|
|
4
|
+
directory: "/"
|
|
5
|
+
schedule:
|
|
6
|
+
interval: "weekly"
|
|
7
|
+
commit-message:
|
|
8
|
+
prefix: "chore"
|
|
9
|
+
|
|
10
|
+
- package-ecosystem: "github-actions"
|
|
11
|
+
directory: "/"
|
|
12
|
+
schedule:
|
|
13
|
+
interval: "weekly"
|
|
14
|
+
commit-message:
|
|
15
|
+
prefix: "ci"
|
|
@@ -6,6 +6,9 @@ on:
|
|
|
6
6
|
pull_request:
|
|
7
7
|
branches: [main]
|
|
8
8
|
|
|
9
|
+
permissions:
|
|
10
|
+
contents: read
|
|
11
|
+
|
|
9
12
|
jobs:
|
|
10
13
|
test:
|
|
11
14
|
runs-on: ubuntu-latest
|
|
@@ -13,13 +16,13 @@ jobs:
|
|
|
13
16
|
matrix:
|
|
14
17
|
python-version: ["3.10", "3.11", "3.12", "3.13", "3.14", "3.15"]
|
|
15
18
|
steps:
|
|
16
|
-
- uses: actions/checkout@
|
|
19
|
+
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
|
17
20
|
|
|
18
21
|
- name: Install uv
|
|
19
|
-
uses: astral-sh/setup-uv@
|
|
22
|
+
uses: astral-sh/setup-uv@08807647e7069bb48b6ef5acd8ec9567f424441b # v8.1.0
|
|
20
23
|
|
|
21
24
|
- name: Set up Python ${{ matrix.python-version }}
|
|
22
|
-
uses: actions/setup-python@
|
|
25
|
+
uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
|
|
23
26
|
with:
|
|
24
27
|
python-version: ${{ matrix.python-version }}
|
|
25
28
|
allow-prereleases: true
|
|
@@ -35,7 +38,7 @@ jobs:
|
|
|
35
38
|
|
|
36
39
|
- name: Upload coverage to Codecov
|
|
37
40
|
if: matrix.python-version == '3.14'
|
|
38
|
-
uses: codecov/codecov-action@v6
|
|
41
|
+
uses: codecov/codecov-action@cddd853df119a48c5be31a973f8cd97e12e35e16 # v6.0.1
|
|
39
42
|
with:
|
|
40
43
|
token: ${{ secrets.CODECOV_TOKEN }}
|
|
41
44
|
files: coverage.xml
|
|
@@ -43,13 +46,13 @@ jobs:
|
|
|
43
46
|
lint:
|
|
44
47
|
runs-on: ubuntu-latest
|
|
45
48
|
steps:
|
|
46
|
-
- uses: actions/checkout@
|
|
49
|
+
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
|
47
50
|
|
|
48
51
|
- name: Install uv
|
|
49
|
-
uses: astral-sh/setup-uv@
|
|
52
|
+
uses: astral-sh/setup-uv@08807647e7069bb48b6ef5acd8ec9567f424441b # v8.1.0
|
|
50
53
|
|
|
51
54
|
- name: Set up Python
|
|
52
|
-
uses: actions/setup-python@
|
|
55
|
+
uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
|
|
53
56
|
with:
|
|
54
57
|
python-version: "3.14"
|
|
55
58
|
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
name: CodeQL
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches: [main]
|
|
6
|
+
pull_request:
|
|
7
|
+
branches: [main]
|
|
8
|
+
schedule:
|
|
9
|
+
- cron: "0 3 * * 1"
|
|
10
|
+
|
|
11
|
+
permissions:
|
|
12
|
+
contents: read
|
|
13
|
+
|
|
14
|
+
jobs:
|
|
15
|
+
analyze:
|
|
16
|
+
runs-on: ubuntu-latest
|
|
17
|
+
permissions:
|
|
18
|
+
security-events: write
|
|
19
|
+
steps:
|
|
20
|
+
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
|
21
|
+
- uses: github/codeql-action/init@f52b05f4acaaa234e44466e66d29050e135ea9ef # v4.36.0
|
|
22
|
+
with:
|
|
23
|
+
languages: python
|
|
24
|
+
- uses: github/codeql-action/analyze@f52b05f4acaaa234e44466e66d29050e135ea9ef # v4.36.0
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
name: Publish to PyPI
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
release:
|
|
5
|
+
types: [published]
|
|
6
|
+
|
|
7
|
+
permissions:
|
|
8
|
+
contents: read
|
|
9
|
+
|
|
10
|
+
jobs:
|
|
11
|
+
publish:
|
|
12
|
+
runs-on: ubuntu-latest
|
|
13
|
+
environment: pypi
|
|
14
|
+
permissions:
|
|
15
|
+
id-token: write
|
|
16
|
+
contents: write
|
|
17
|
+
attestations: write
|
|
18
|
+
steps:
|
|
19
|
+
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
|
20
|
+
|
|
21
|
+
- name: Install uv
|
|
22
|
+
uses: astral-sh/setup-uv@08807647e7069bb48b6ef5acd8ec9567f424441b # v8.1.0
|
|
23
|
+
|
|
24
|
+
- name: Set up Python
|
|
25
|
+
uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
|
|
26
|
+
with:
|
|
27
|
+
python-version: "3.15"
|
|
28
|
+
allow-prereleases: true
|
|
29
|
+
|
|
30
|
+
- name: Build
|
|
31
|
+
run: uv build
|
|
32
|
+
|
|
33
|
+
- name: Attest build provenance
|
|
34
|
+
uses: actions/attest-build-provenance@a2bbfa25375fe432b6a289bc6b6cd05ecd0c4c32 # v4.1.0
|
|
35
|
+
with:
|
|
36
|
+
subject-path: dist/*
|
|
37
|
+
|
|
38
|
+
- name: Publish to PyPI
|
|
39
|
+
uses: pypa/gh-action-pypi-publish@cef221092ed1bacb1cc03d23a2d87d1d172e277b # v1.14.0
|
|
40
|
+
|
|
41
|
+
- name: Upload to GitHub Release
|
|
42
|
+
run: gh release upload "${{ github.event.release.tag_name }}" dist/*
|
|
43
|
+
env:
|
|
44
|
+
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
name: Scorecard
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches: [main]
|
|
6
|
+
workflow_dispatch:
|
|
7
|
+
schedule:
|
|
8
|
+
- cron: "30 1 * * 6"
|
|
9
|
+
|
|
10
|
+
permissions: read-all
|
|
11
|
+
|
|
12
|
+
jobs:
|
|
13
|
+
analysis:
|
|
14
|
+
runs-on: ubuntu-latest
|
|
15
|
+
permissions:
|
|
16
|
+
security-events: write
|
|
17
|
+
id-token: write
|
|
18
|
+
steps:
|
|
19
|
+
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
|
20
|
+
with:
|
|
21
|
+
persist-credentials: false
|
|
22
|
+
- uses: ossf/scorecard-action@4eaacf0543bb3f2c246792bd56e8cdeffafb205a # v2.4.3
|
|
23
|
+
with:
|
|
24
|
+
results_file: results.sarif
|
|
25
|
+
results_format: sarif
|
|
26
|
+
publish_results: true
|
|
27
|
+
- uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
|
|
28
|
+
with:
|
|
29
|
+
name: SARIF file
|
|
30
|
+
path: results.sarif
|
|
31
|
+
retention-days: 5
|
|
32
|
+
- uses: github/codeql-action/upload-sarif@e46ed2cbd01164d986452f91f178727624ae40d7 # v4.35.3
|
|
33
|
+
with:
|
|
34
|
+
sarif_file: results.sarif
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: assertpy2
|
|
3
|
-
Version: 2.1.
|
|
3
|
+
Version: 2.1.3
|
|
4
4
|
Summary: Fluent assertion library for Python with full type safety and soft assertions
|
|
5
5
|
Project-URL: Homepage, https://github.com/Solganis/assertpy2
|
|
6
6
|
Project-URL: Repository, https://github.com/Solganis/assertpy2
|
|
@@ -50,6 +50,9 @@ Description-Content-Type: text/markdown
|
|
|
50
50
|
<a href="https://codecov.io/gh/Solganis/assertpy2"><img src="https://codecov.io/gh/Solganis/assertpy2/graph/badge.svg" alt="Coverage"></a>
|
|
51
51
|
<a href="https://github.com/Solganis/assertpy2/blob/main/LICENSE"><img src="https://img.shields.io/github/license/Solganis/assertpy2" alt="License"></a>
|
|
52
52
|
<a href="https://docs.astral.sh/ruff/"><img src="https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/astral-sh/ruff/main/assets/badge/v2.json" alt="Ruff"></a>
|
|
53
|
+
<a href="https://github.com/astral-sh/uv"><img src="https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/astral-sh/uv/main/assets/badge/v0.json" alt="uv"></a>
|
|
54
|
+
<a href="https://scorecard.dev/viewer/?uri=github.com/Solganis/assertpy2"><img src="https://api.scorecard.dev/projects/github.com/Solganis/assertpy2/badge" alt="OpenSSF Scorecard"></a>
|
|
55
|
+
<a href="https://www.bestpractices.dev/projects/12990"><img src="https://www.bestpractices.dev/projects/12990/badge" alt="OpenSSF Best Practices"></a>
|
|
53
56
|
</p>
|
|
54
57
|
|
|
55
58
|
|
|
@@ -109,6 +112,25 @@ FAILED test_example.py::test_comparison
|
|
|
109
112
|
```
|
|
110
113
|
|
|
111
114
|
|
|
115
|
+
## Comparison
|
|
116
|
+
|
|
117
|
+
<div align="center">
|
|
118
|
+
|
|
119
|
+
| | pytest assert | PyHamcrest | assertpy | **assertpy2** |
|
|
120
|
+
|---|:---:|:---:|:---:|:---:|
|
|
121
|
+
| **Type safety** | Partial (mypy plugin) | No | No | **py.typed, @overload, Self** |
|
|
122
|
+
| **IDE autocomplete** | Generic | Generic | Generic | **Type-specific per value** |
|
|
123
|
+
| **Fluent chaining** | No | No | Yes | **Yes** |
|
|
124
|
+
| **Composable matchers** | No | Yes (functions) | No | **Yes (`&` `\|` `~` operators)** |
|
|
125
|
+
| **Structural matching** | No | Flat (has_entries) | No | **Recursive with matchers** |
|
|
126
|
+
| **Async assertions** | No | No | No | **eventually() with polling** |
|
|
127
|
+
| **Soft assertions** | No | No | Yes (not thread-safe) | **Yes (thread-safe, async-safe)** |
|
|
128
|
+
| **Structured errors** | Rewrite only | Mismatch string | String only | **.actual .expected .diff** |
|
|
129
|
+
| **Maintained** | N/A | Minimal | 2020 | **Active (2026)** |
|
|
130
|
+
|
|
131
|
+
</div>
|
|
132
|
+
|
|
133
|
+
|
|
112
134
|
## Why fluent assertions?
|
|
113
135
|
|
|
114
136
|
```py
|
|
@@ -350,24 +372,7 @@ from assertpy import assert_that, soft_assertions
|
|
|
350
372
|
from assertpy2 import assert_that, soft_assertions
|
|
351
373
|
```
|
|
352
374
|
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
| | assertpy | assertpy2 |
|
|
356
|
-
|---|---|---|
|
|
357
|
-
| Maintained | Last release 2020 | Active |
|
|
358
|
-
| Python | 2.7+ | 3.10-3.15 |
|
|
359
|
-
| Type safety | No annotations | `Self` return types, `py.typed`, typed `@overload` on `assert_that()` |
|
|
360
|
-
| IDE support | No type info | Full autocomplete, type-specific method suggestions |
|
|
361
|
-
| Matchers | None | Composable matchers with `&` `\|` `~` operators |
|
|
362
|
-
| Structural matching | None | `matches_structure()` with recursive dict validation |
|
|
363
|
-
| Async | None | `eventually()` with polling/retry |
|
|
364
|
-
| Error reporting | Flat strings | `AssertionFailure` with `.actual`, `.expected`, `.diff` |
|
|
365
|
-
| Pytest integration | None | Rich diff sections in failure reports |
|
|
366
|
-
| Soft assertions | Global state, not thread-safe | `contextvars`, thread-safe and async-safe |
|
|
367
|
-
| Security | [CVE in snapshots](https://github.com/assertpy/assertpy/issues/156) | Fixed |
|
|
368
|
-
| Open bugs | 15+ unresolved | All resolved |
|
|
369
|
-
|
|
370
|
-
</div>
|
|
375
|
+
See the [comparison table](#comparison) above for feature differences with other libraries.
|
|
371
376
|
|
|
372
377
|
|
|
373
378
|
## Contributing
|
|
@@ -19,6 +19,9 @@
|
|
|
19
19
|
<a href="https://codecov.io/gh/Solganis/assertpy2"><img src="https://codecov.io/gh/Solganis/assertpy2/graph/badge.svg" alt="Coverage"></a>
|
|
20
20
|
<a href="https://github.com/Solganis/assertpy2/blob/main/LICENSE"><img src="https://img.shields.io/github/license/Solganis/assertpy2" alt="License"></a>
|
|
21
21
|
<a href="https://docs.astral.sh/ruff/"><img src="https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/astral-sh/ruff/main/assets/badge/v2.json" alt="Ruff"></a>
|
|
22
|
+
<a href="https://github.com/astral-sh/uv"><img src="https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/astral-sh/uv/main/assets/badge/v0.json" alt="uv"></a>
|
|
23
|
+
<a href="https://scorecard.dev/viewer/?uri=github.com/Solganis/assertpy2"><img src="https://api.scorecard.dev/projects/github.com/Solganis/assertpy2/badge" alt="OpenSSF Scorecard"></a>
|
|
24
|
+
<a href="https://www.bestpractices.dev/projects/12990"><img src="https://www.bestpractices.dev/projects/12990/badge" alt="OpenSSF Best Practices"></a>
|
|
22
25
|
</p>
|
|
23
26
|
|
|
24
27
|
|
|
@@ -78,6 +81,25 @@ FAILED test_example.py::test_comparison
|
|
|
78
81
|
```
|
|
79
82
|
|
|
80
83
|
|
|
84
|
+
## Comparison
|
|
85
|
+
|
|
86
|
+
<div align="center">
|
|
87
|
+
|
|
88
|
+
| | pytest assert | PyHamcrest | assertpy | **assertpy2** |
|
|
89
|
+
|---|:---:|:---:|:---:|:---:|
|
|
90
|
+
| **Type safety** | Partial (mypy plugin) | No | No | **py.typed, @overload, Self** |
|
|
91
|
+
| **IDE autocomplete** | Generic | Generic | Generic | **Type-specific per value** |
|
|
92
|
+
| **Fluent chaining** | No | No | Yes | **Yes** |
|
|
93
|
+
| **Composable matchers** | No | Yes (functions) | No | **Yes (`&` `\|` `~` operators)** |
|
|
94
|
+
| **Structural matching** | No | Flat (has_entries) | No | **Recursive with matchers** |
|
|
95
|
+
| **Async assertions** | No | No | No | **eventually() with polling** |
|
|
96
|
+
| **Soft assertions** | No | No | Yes (not thread-safe) | **Yes (thread-safe, async-safe)** |
|
|
97
|
+
| **Structured errors** | Rewrite only | Mismatch string | String only | **.actual .expected .diff** |
|
|
98
|
+
| **Maintained** | N/A | Minimal | 2020 | **Active (2026)** |
|
|
99
|
+
|
|
100
|
+
</div>
|
|
101
|
+
|
|
102
|
+
|
|
81
103
|
## Why fluent assertions?
|
|
82
104
|
|
|
83
105
|
```py
|
|
@@ -319,24 +341,7 @@ from assertpy import assert_that, soft_assertions
|
|
|
319
341
|
from assertpy2 import assert_that, soft_assertions
|
|
320
342
|
```
|
|
321
343
|
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
| | assertpy | assertpy2 |
|
|
325
|
-
|---|---|---|
|
|
326
|
-
| Maintained | Last release 2020 | Active |
|
|
327
|
-
| Python | 2.7+ | 3.10-3.15 |
|
|
328
|
-
| Type safety | No annotations | `Self` return types, `py.typed`, typed `@overload` on `assert_that()` |
|
|
329
|
-
| IDE support | No type info | Full autocomplete, type-specific method suggestions |
|
|
330
|
-
| Matchers | None | Composable matchers with `&` `\|` `~` operators |
|
|
331
|
-
| Structural matching | None | `matches_structure()` with recursive dict validation |
|
|
332
|
-
| Async | None | `eventually()` with polling/retry |
|
|
333
|
-
| Error reporting | Flat strings | `AssertionFailure` with `.actual`, `.expected`, `.diff` |
|
|
334
|
-
| Pytest integration | None | Rich diff sections in failure reports |
|
|
335
|
-
| Soft assertions | Global state, not thread-safe | `contextvars`, thread-safe and async-safe |
|
|
336
|
-
| Security | [CVE in snapshots](https://github.com/assertpy/assertpy/issues/156) | Fixed |
|
|
337
|
-
| Open bugs | 15+ unresolved | All resolved |
|
|
338
|
-
|
|
339
|
-
</div>
|
|
344
|
+
See the [comparison table](#comparison) above for feature differences with other libraries.
|
|
340
345
|
|
|
341
346
|
|
|
342
347
|
## Contributing
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
# Security Policy
|
|
2
|
+
|
|
3
|
+
## Supported Versions
|
|
4
|
+
|
|
5
|
+
| Version | Supported |
|
|
6
|
+
|---|---|
|
|
7
|
+
| 2.x | Yes |
|
|
8
|
+
| < 2.0 | No |
|
|
9
|
+
|
|
10
|
+
## Reporting a Vulnerability
|
|
11
|
+
|
|
12
|
+
Please report security vulnerabilities by emailing solganis.dev@gmail.com.
|
|
13
|
+
|
|
14
|
+
Do not open a public issue for security vulnerabilities.
|
|
15
|
+
|
|
16
|
+
You should receive a response within 48 hours. If the vulnerability is confirmed, a fix will be released as soon as possible.
|
|
@@ -73,7 +73,7 @@ from .numeric import NumericMixin
|
|
|
73
73
|
from .snapshot import SnapshotMixin
|
|
74
74
|
from .string import StringMixin
|
|
75
75
|
|
|
76
|
-
__version__ = "2.1.
|
|
76
|
+
__version__ = "2.1.3"
|
|
77
77
|
|
|
78
78
|
__tracebackhide__ = True # clean tracebacks via py.test integration
|
|
79
79
|
contextlib.__tracebackhide__ = True # monkey patch contextlib with clean py.test tracebacks
|
|
@@ -490,8 +490,6 @@ class TestContainsWithMatcher:
|
|
|
490
490
|
|
|
491
491
|
|
|
492
492
|
class TestDescribeCoverage:
|
|
493
|
-
"""Ensures all matcher describe() methods are exercised."""
|
|
494
|
-
|
|
495
493
|
def test_greater_than_or_equal_to_describe(self):
|
|
496
494
|
assert_that(match.greater_than_or_equal_to(5).describe()).contains("greater than or equal to")
|
|
497
495
|
|
|
@@ -6,8 +6,6 @@ from assertpy2.assertpy import AssertionBuilder
|
|
|
6
6
|
|
|
7
7
|
|
|
8
8
|
class TestOverloadsRuntimeBehavior:
|
|
9
|
-
"""Verify that @overload decorators don't affect runtime behavior."""
|
|
10
|
-
|
|
11
9
|
def test_str_returns_builder(self):
|
|
12
10
|
result = assert_that("hello")
|
|
13
11
|
assert isinstance(result, AssertionBuilder)
|
|
@@ -1,29 +0,0 @@
|
|
|
1
|
-
name: Publish to PyPI
|
|
2
|
-
|
|
3
|
-
on:
|
|
4
|
-
release:
|
|
5
|
-
types: [published]
|
|
6
|
-
|
|
7
|
-
jobs:
|
|
8
|
-
publish:
|
|
9
|
-
runs-on: ubuntu-latest
|
|
10
|
-
environment: pypi
|
|
11
|
-
permissions:
|
|
12
|
-
id-token: write
|
|
13
|
-
steps:
|
|
14
|
-
- uses: actions/checkout@v4
|
|
15
|
-
|
|
16
|
-
- name: Install uv
|
|
17
|
-
uses: astral-sh/setup-uv@v6
|
|
18
|
-
|
|
19
|
-
- name: Set up Python
|
|
20
|
-
uses: actions/setup-python@v5
|
|
21
|
-
with:
|
|
22
|
-
python-version: "3.15"
|
|
23
|
-
allow-prereleases: true
|
|
24
|
-
|
|
25
|
-
- name: Build
|
|
26
|
-
run: uv build
|
|
27
|
-
|
|
28
|
-
- name: Publish to PyPI
|
|
29
|
-
uses: pypa/gh-action-pypi-publish@release/v1
|
assertpy2-2.1.1/CLAUDE.md
DELETED
|
@@ -1,113 +0,0 @@
|
|
|
1
|
-
# assertpy2
|
|
2
|
-
|
|
3
|
-
Fluent assertion library for Python. Fork of assertpy с type safety, soft assertions, snapshot testing.
|
|
4
|
-
Python 3.10+, единственная зависимость: typing_extensions>=4.0.
|
|
5
|
-
|
|
6
|
-
---
|
|
7
|
-
|
|
8
|
-
## Project layout
|
|
9
|
-
|
|
10
|
-
```
|
|
11
|
-
assertpy2/
|
|
12
|
-
├── assertpy.py # assert_that(), AssertionBuilder, soft_assertions(), fail()
|
|
13
|
-
├── async_assertions.py # AsyncAssertionBuilder - eventually() polling
|
|
14
|
-
├── base.py # BaseMixin - is_equal_to, satisfies, each, matches_structure
|
|
15
|
-
├── string.py # StringMixin - starts_with, ends_with, matches, is_alpha, is_digit
|
|
16
|
-
├── numeric.py # NumericMixin - is_zero, is_positive, is_between, is_close_to
|
|
17
|
-
├── collection.py # CollectionMixin - is_iterable, is_subset_of, is_sorted
|
|
18
|
-
├── contains.py # ContainsMixin - contains, does_not_contain, is_empty
|
|
19
|
-
├── dict.py # DictMixin - contains_key, contains_value, contains_entry
|
|
20
|
-
├── date.py # DateMixin - is_before, is_after, is_equal_to_ignoring_time
|
|
21
|
-
├── file.py # FileMixin + contents_of() - exists, is_file, is_directory
|
|
22
|
-
├── exception.py # ExceptionMixin - raises, when_called_with
|
|
23
|
-
├── extracting.py # ExtractingMixin - extracting with filter/sort
|
|
24
|
-
├── dynamic.py # DynamicMixin - has_<attr>() via __getattr__
|
|
25
|
-
├── snapshot.py # SnapshotMixin - snapshot testing
|
|
26
|
-
├── helpers.py # HelpersMixin - _dict_not_equal, _fmt_items, validation
|
|
27
|
-
├── errors.py # AssertionFailure, DiffResult, DiffEntry - structured errors
|
|
28
|
-
├── matchers.py # Matcher protocol, composable matchers, match namespace
|
|
29
|
-
├── pytest_plugin.py # pytest entry point - rich diff output for AssertionFailure
|
|
30
|
-
├── _typing.py # TYPE_CHECKING-only Protocol classes for @overload return types
|
|
31
|
-
├── __init__.py # Public API exports
|
|
32
|
-
└── py.typed # PEP 561 marker
|
|
33
|
-
|
|
34
|
-
tests/ # 100% coverage required
|
|
35
|
-
```
|
|
36
|
-
|
|
37
|
-
---
|
|
38
|
-
|
|
39
|
-
## Architecture
|
|
40
|
-
|
|
41
|
-
- AssertionBuilder наследует миксины. Каждый миксин - одна категория assertions.
|
|
42
|
-
- Все assertion-методы возвращают Self для chaining.
|
|
43
|
-
- error() в AssertionBuilder маршрутизирует: raise (default), log (warn), collect (soft).
|
|
44
|
-
- __tracebackhide__ = True во всех миксинах для чистого pytest traceback.
|
|
45
|
-
- Расширения через add_extension(func) - динамическая привязка через types.MethodType.
|
|
46
|
-
|
|
47
|
-
Правило: новые assertions добавляются в существующий миксин по категории.
|
|
48
|
-
Новый миксин создаётся только для принципиально новой категории (не для 1-2 методов).
|
|
49
|
-
|
|
50
|
-
---
|
|
51
|
-
|
|
52
|
-
## Tooling
|
|
53
|
-
|
|
54
|
-
```
|
|
55
|
-
uv sync
|
|
56
|
-
uv run pytest
|
|
57
|
-
uv run pytest -v --cov=assertpy2 --cov-report=term-missing
|
|
58
|
-
uv run ruff check .
|
|
59
|
-
uv run ruff format .
|
|
60
|
-
uv run ruff format --check .
|
|
61
|
-
```
|
|
62
|
-
|
|
63
|
-
---
|
|
64
|
-
|
|
65
|
-
## CI/CD
|
|
66
|
-
|
|
67
|
-
GitHub Actions, два workflow:
|
|
68
|
-
|
|
69
|
-
**CI** (.github/workflows/ci.yml):
|
|
70
|
-
- Триггер: push/PR в main
|
|
71
|
-
- Матрица: Python 3.10-3.15
|
|
72
|
-
- Шаги: uv sync, ruff check, pytest с coverage (xml + term-missing)
|
|
73
|
-
- Codecov upload: только с Python 3.14 (token в secrets)
|
|
74
|
-
- Отдельный lint job: ruff check + ruff format --check
|
|
75
|
-
|
|
76
|
-
**Publish** (.github/workflows/publish.yml):
|
|
77
|
-
- Триггер: GitHub Release (published)
|
|
78
|
-
- Trusted Publisher (id-token: write), без API-ключей
|
|
79
|
-
- uv build, pypa/gh-action-pypi-publish
|
|
80
|
-
|
|
81
|
-
Правила:
|
|
82
|
-
- Версия только в pyproject.toml (одно место)
|
|
83
|
-
- Релиз: обновить version в pyproject.toml, создать GitHub Release с тегом
|
|
84
|
-
- Не мержить без зелёного CI
|
|
85
|
-
|
|
86
|
-
---
|
|
87
|
-
|
|
88
|
-
## Key dependencies
|
|
89
|
-
|
|
90
|
-
| Package | Version | Role |
|
|
91
|
-
|---|---|---|
|
|
92
|
-
| typing_extensions | >=4.0 | Self type, runtime-free typing |
|
|
93
|
-
| pytest | >=9.0.3 | test runner (dev) |
|
|
94
|
-
| pytest-cov | >=6.1 | coverage (dev) |
|
|
95
|
-
| ruff | >=0.15.14 | linter + formatter (dev) |
|
|
96
|
-
|
|
97
|
-
---
|
|
98
|
-
|
|
99
|
-
## Naming
|
|
100
|
-
|
|
101
|
-
- Assertion-методы: is_*, has_*, does_not_*, contains_*, starts_with, ends_with
|
|
102
|
-
- Новые assertions следуют существующему паттерну: глагол + предикат
|
|
103
|
-
- Тестовые файлы: test_<feature>.py
|
|
104
|
-
- Миксины: <Category>Mixin
|
|
105
|
-
|
|
106
|
-
---
|
|
107
|
-
|
|
108
|
-
## Testing
|
|
109
|
-
|
|
110
|
-
- Coverage 100%. Каждый assertion-метод покрыт: happy path, error path, edge cases.
|
|
111
|
-
- Тесты используют pytest.raises(AssertionError) для проверки сообщений об ошибках.
|
|
112
|
-
- match= в pytest.raises для валидации текста ошибки.
|
|
113
|
-
- Snapshot-тесты хранят данные в tests/__snapshots__/.
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|