assertpy2 2.1.4__tar.gz → 2.3.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.1.4 → assertpy2-2.3.0}/.github/workflows/ci.yml +5 -5
- {assertpy2-2.1.4 → assertpy2-2.3.0}/.github/workflows/codeql.yml +1 -1
- {assertpy2-2.1.4 → assertpy2-2.3.0}/.github/workflows/publish.yml +2 -2
- {assertpy2-2.1.4 → assertpy2-2.3.0}/.github/workflows/scorecard.yml +1 -1
- {assertpy2-2.1.4 → assertpy2-2.3.0}/PKG-INFO +62 -8
- {assertpy2-2.1.4 → assertpy2-2.3.0}/README.md +57 -7
- {assertpy2-2.1.4 → assertpy2-2.3.0}/assertpy2/_typing.py +19 -0
- {assertpy2-2.1.4 → assertpy2-2.3.0}/assertpy2/assertpy.py +5 -5
- {assertpy2-2.1.4 → assertpy2-2.3.0}/assertpy2/async_assertions.py +2 -2
- {assertpy2-2.1.4 → assertpy2-2.3.0}/assertpy2/base.py +157 -17
- assertpy2-2.3.0/assertpy2/behave_matchers.py +85 -0
- {assertpy2-2.1.4 → assertpy2-2.3.0}/assertpy2/collection.py +9 -19
- {assertpy2-2.1.4 → assertpy2-2.3.0}/assertpy2/contains.py +88 -27
- {assertpy2-2.1.4 → assertpy2-2.3.0}/assertpy2/date.py +88 -20
- {assertpy2-2.1.4 → assertpy2-2.3.0}/assertpy2/dict.py +8 -8
- {assertpy2-2.1.4 → assertpy2-2.3.0}/assertpy2/dynamic.py +7 -7
- {assertpy2-2.1.4 → assertpy2-2.3.0}/assertpy2/errors.py +2 -2
- {assertpy2-2.1.4 → assertpy2-2.3.0}/assertpy2/exception.py +51 -15
- {assertpy2-2.1.4 → assertpy2-2.3.0}/assertpy2/extracting.py +5 -5
- {assertpy2-2.1.4 → assertpy2-2.3.0}/assertpy2/file.py +12 -12
- {assertpy2-2.1.4 → assertpy2-2.3.0}/assertpy2/helpers.py +61 -42
- {assertpy2-2.1.4 → assertpy2-2.3.0}/assertpy2/matchers.py +156 -41
- {assertpy2-2.1.4 → assertpy2-2.3.0}/assertpy2/numeric.py +106 -41
- assertpy2-2.3.0/assertpy2/pytest_plugin.py +115 -0
- {assertpy2-2.1.4 → assertpy2-2.3.0}/assertpy2/snapshot.py +1 -1
- {assertpy2-2.1.4 → assertpy2-2.3.0}/assertpy2/string.py +123 -17
- {assertpy2-2.1.4 → assertpy2-2.3.0}/docs/api.md +190 -4
- {assertpy2-2.1.4 → assertpy2-2.3.0}/pyproject.toml +7 -3
- {assertpy2-2.1.4 → assertpy2-2.3.0}/tests/test_async.py +5 -5
- assertpy2-2.3.0/tests/test_behave_matchers.py +108 -0
- {assertpy2-2.1.4 → assertpy2-2.3.0}/tests/test_class.py +3 -3
- {assertpy2-2.1.4 → assertpy2-2.3.0}/tests/test_dyn.py +3 -3
- {assertpy2-2.1.4 → assertpy2-2.3.0}/tests/test_errors.py +29 -1
- {assertpy2-2.1.4 → assertpy2-2.3.0}/tests/test_expected_exception.py +2 -3
- {assertpy2-2.1.4 → assertpy2-2.3.0}/tests/test_extensions.py +5 -5
- {assertpy2-2.1.4 → assertpy2-2.3.0}/tests/test_extracting.py +2 -2
- assertpy2-2.3.0/tests/test_matchers_phase3.py +266 -0
- {assertpy2-2.1.4 → assertpy2-2.3.0}/tests/test_numbers.py +141 -1
- assertpy2-2.3.0/tests/test_phase2.py +422 -0
- assertpy2-2.3.0/tests/test_pytest_plugin.py +383 -0
- {assertpy2-2.1.4 → assertpy2-2.3.0}/tests/test_readme.py +4 -4
- {assertpy2-2.1.4 → assertpy2-2.3.0}/tests/test_string.py +180 -0
- {assertpy2-2.1.4 → assertpy2-2.3.0}/uv.lock +137 -22
- assertpy2-2.1.4/assertpy2/pytest_plugin.py +0 -42
- assertpy2-2.1.4/tests/test_pytest_plugin.py +0 -154
- {assertpy2-2.1.4 → assertpy2-2.3.0}/.codecov.yml +0 -0
- {assertpy2-2.1.4 → assertpy2-2.3.0}/.github/dependabot.yml +0 -0
- {assertpy2-2.1.4 → assertpy2-2.3.0}/.gitignore +0 -0
- {assertpy2-2.1.4 → assertpy2-2.3.0}/CONTRIBUTING.md +0 -0
- {assertpy2-2.1.4 → assertpy2-2.3.0}/LICENSE +0 -0
- {assertpy2-2.1.4 → assertpy2-2.3.0}/SECURITY.md +0 -0
- {assertpy2-2.1.4 → assertpy2-2.3.0}/assertpy2/__init__.py +0 -0
- {assertpy2-2.1.4 → assertpy2-2.3.0}/assertpy2/py.typed +0 -0
- {assertpy2-2.1.4 → assertpy2-2.3.0}/docs/logo-dark.svg +0 -0
- {assertpy2-2.1.4 → assertpy2-2.3.0}/docs/logo.svg +0 -0
- {assertpy2-2.1.4 → assertpy2-2.3.0}/tests/test_bool.py +0 -0
- {assertpy2-2.1.4 → assertpy2-2.3.0}/tests/test_collection.py +0 -0
- {assertpy2-2.1.4 → assertpy2-2.3.0}/tests/test_core.py +0 -0
- {assertpy2-2.1.4 → assertpy2-2.3.0}/tests/test_custom_dict.py +0 -0
- {assertpy2-2.1.4 → assertpy2-2.3.0}/tests/test_custom_list.py +0 -0
- {assertpy2-2.1.4 → assertpy2-2.3.0}/tests/test_datetime.py +0 -0
- {assertpy2-2.1.4 → assertpy2-2.3.0}/tests/test_description.py +0 -0
- {assertpy2-2.1.4 → assertpy2-2.3.0}/tests/test_dict.py +0 -0
- {assertpy2-2.1.4 → assertpy2-2.3.0}/tests/test_dict_compare.py +0 -0
- {assertpy2-2.1.4 → assertpy2-2.3.0}/tests/test_equals.py +0 -0
- {assertpy2-2.1.4 → assertpy2-2.3.0}/tests/test_fail.py +0 -0
- {assertpy2-2.1.4 → assertpy2-2.3.0}/tests/test_file.py +0 -0
- {assertpy2-2.1.4 → assertpy2-2.3.0}/tests/test_in.py +0 -0
- {assertpy2-2.1.4 → assertpy2-2.3.0}/tests/test_list.py +0 -0
- {assertpy2-2.1.4 → assertpy2-2.3.0}/tests/test_matchers.py +0 -0
- {assertpy2-2.1.4 → assertpy2-2.3.0}/tests/test_namedtuple.py +0 -0
- {assertpy2-2.1.4 → assertpy2-2.3.0}/tests/test_none.py +0 -0
- {assertpy2-2.1.4 → assertpy2-2.3.0}/tests/test_overloads.py +0 -0
- {assertpy2-2.1.4 → assertpy2-2.3.0}/tests/test_same_as.py +0 -0
- {assertpy2-2.1.4 → assertpy2-2.3.0}/tests/test_snapshots.py +0 -0
- {assertpy2-2.1.4 → assertpy2-2.3.0}/tests/test_soft.py +0 -0
- {assertpy2-2.1.4 → assertpy2-2.3.0}/tests/test_soft_fail.py +0 -0
- {assertpy2-2.1.4 → assertpy2-2.3.0}/tests/test_structural.py +0 -0
- {assertpy2-2.1.4 → assertpy2-2.3.0}/tests/test_traceback.py +0 -0
- {assertpy2-2.1.4 → assertpy2-2.3.0}/tests/test_type.py +0 -0
- {assertpy2-2.1.4 → assertpy2-2.3.0}/tests/test_warn.py +0 -0
|
@@ -16,10 +16,10 @@ jobs:
|
|
|
16
16
|
matrix:
|
|
17
17
|
python-version: ["3.10", "3.11", "3.12", "3.13", "3.14", "3.15"]
|
|
18
18
|
steps:
|
|
19
|
-
- uses: actions/checkout@
|
|
19
|
+
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
|
|
20
20
|
|
|
21
21
|
- name: Install uv
|
|
22
|
-
uses: astral-sh/setup-uv@
|
|
22
|
+
uses: astral-sh/setup-uv@fac544c07dec837d0ccb6301d7b5580bf5edae39 # v8.2.0
|
|
23
23
|
|
|
24
24
|
- name: Set up Python ${{ matrix.python-version }}
|
|
25
25
|
uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
|
|
@@ -38,7 +38,7 @@ jobs:
|
|
|
38
38
|
|
|
39
39
|
- name: Upload coverage to Codecov
|
|
40
40
|
if: matrix.python-version == '3.14'
|
|
41
|
-
uses: codecov/codecov-action@
|
|
41
|
+
uses: codecov/codecov-action@e79a6962e0d4c0c17b229090214935d2e33f8354 # v6.0.1
|
|
42
42
|
with:
|
|
43
43
|
token: ${{ secrets.CODECOV_TOKEN }}
|
|
44
44
|
files: coverage.xml
|
|
@@ -46,10 +46,10 @@ jobs:
|
|
|
46
46
|
lint:
|
|
47
47
|
runs-on: ubuntu-latest
|
|
48
48
|
steps:
|
|
49
|
-
- uses: actions/checkout@
|
|
49
|
+
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
|
|
50
50
|
|
|
51
51
|
- name: Install uv
|
|
52
|
-
uses: astral-sh/setup-uv@
|
|
52
|
+
uses: astral-sh/setup-uv@fac544c07dec837d0ccb6301d7b5580bf5edae39 # v8.2.0
|
|
53
53
|
|
|
54
54
|
- name: Set up Python
|
|
55
55
|
uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
|
|
@@ -17,7 +17,7 @@ jobs:
|
|
|
17
17
|
permissions:
|
|
18
18
|
security-events: write
|
|
19
19
|
steps:
|
|
20
|
-
- uses: actions/checkout@
|
|
20
|
+
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
|
|
21
21
|
- uses: github/codeql-action/init@f52b05f4acaaa234e44466e66d29050e135ea9ef # v4.36.0
|
|
22
22
|
with:
|
|
23
23
|
languages: python
|
|
@@ -16,10 +16,10 @@ jobs:
|
|
|
16
16
|
contents: write
|
|
17
17
|
attestations: write
|
|
18
18
|
steps:
|
|
19
|
-
- uses: actions/checkout@
|
|
19
|
+
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
|
|
20
20
|
|
|
21
21
|
- name: Install uv
|
|
22
|
-
uses: astral-sh/setup-uv@
|
|
22
|
+
uses: astral-sh/setup-uv@fac544c07dec837d0ccb6301d7b5580bf5edae39 # v8.2.0
|
|
23
23
|
|
|
24
24
|
- name: Set up Python
|
|
25
25
|
uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
|
|
@@ -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@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
|
|
20
20
|
with:
|
|
21
21
|
persist-credentials: false
|
|
22
22
|
- uses: ossf/scorecard-action@4eaacf0543bb3f2c246792bd56e8cdeffafb205a # v2.4.3
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: assertpy2
|
|
3
|
-
Version: 2.
|
|
3
|
+
Version: 2.3.0
|
|
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
|
|
@@ -27,6 +27,10 @@ Classifier: Topic :: Software Development
|
|
|
27
27
|
Classifier: Topic :: Software Development :: Testing
|
|
28
28
|
Requires-Python: >=3.10
|
|
29
29
|
Requires-Dist: typing-extensions>=4.0
|
|
30
|
+
Provides-Extra: allure
|
|
31
|
+
Requires-Dist: allure-pytest>=2.13; extra == 'allure'
|
|
32
|
+
Provides-Extra: behave
|
|
33
|
+
Requires-Dist: behave>=1.2.6; extra == 'behave'
|
|
30
34
|
Description-Content-Type: text/markdown
|
|
31
35
|
|
|
32
36
|
<p align="center">
|
|
@@ -126,7 +130,7 @@ FAILED test_example.py::test_comparison
|
|
|
126
130
|
| **Async assertions** | No | No | No | **eventually() with polling** |
|
|
127
131
|
| **Soft assertions** | No | No | Yes (not thread-safe) | **Yes (thread-safe, async-safe)** |
|
|
128
132
|
| **Structured errors** | Rewrite only | Mismatch string | String only | **.actual .expected .diff** |
|
|
129
|
-
| **Maintained** | N/A | Minimal | 2020 | **Active
|
|
133
|
+
| **Maintained** | N/A | Minimal | 2020 | **Active** |
|
|
130
134
|
|
|
131
135
|
</div>
|
|
132
136
|
|
|
@@ -167,6 +171,8 @@ assert_that(items).is_type_of(list).is_length(3).contains("admin")
|
|
|
167
171
|
- **Extracting**: flatten collections on attributes with `filter` and `sort` support.
|
|
168
172
|
- **File assertions**: `exists()`, `is_file()`, `is_readable()`, `is_writable()`, `is_executable()` with `pathlib.Path` support.
|
|
169
173
|
- **Snapshot testing**: store and compare data structures in JSON format, inspired by Jest.
|
|
174
|
+
- **Allure integration**: auto-attach structured diff and actual/expected data to Allure reports.
|
|
175
|
+
- **Behave step matchers**: ready-made parameter types (`PositiveInt`, `BoolLike`, etc.) for Behave step definitions.
|
|
170
176
|
- **Extensions**: add custom assertions via `add_extension()`.
|
|
171
177
|
- Strings, numbers, lists, tuples, sets, dicts, dates, booleans, objects, exceptions.
|
|
172
178
|
|
|
@@ -194,7 +200,7 @@ assert_that("hello").satisfies(~match.equal_to("world"))
|
|
|
194
200
|
assert_that(150).satisfies(match.is_negative() | match.greater_than(100))
|
|
195
201
|
```
|
|
196
202
|
|
|
197
|
-
Available matchers: `equal_to`, `greater_than`, `greater_than_or_equal_to`, `less_than`, `less_than_or_equal_to`, `between`, `close_to`, `is_none`, `is_not_none`, `is_instance_of`, `has_length`, `is_empty`, `is_not_empty`, `is_positive`, `is_negative`, `contains_string`, `matches_regex`, `is_uuid`, `is_non_empty_string`, `ignore`, `each_item`, `structure`.
|
|
203
|
+
Available matchers: `equal_to`, `greater_than`, `greater_than_or_equal_to`, `less_than`, `less_than_or_equal_to`, `between`, `close_to`, `is_none`, `is_not_none`, `is_instance_of`, `has_length`, `is_empty`, `is_not_empty`, `is_positive`, `is_negative`, `is_zero`, `is_even`, `is_odd`, `is_divisible_by`, `is_callable`, `is_in`, `has_property`, `contains_string`, `matches_regex`, `is_uuid`, `is_non_empty_string`, `ignore`, `each_item`, `structure`.
|
|
198
204
|
|
|
199
205
|
|
|
200
206
|
## Structural matching
|
|
@@ -347,19 +353,67 @@ assert_that({"a": 1, "b": 2, "c": 3}).snapshot()
|
|
|
347
353
|
```py
|
|
348
354
|
from assertpy2 import add_extension
|
|
349
355
|
|
|
350
|
-
def
|
|
351
|
-
if self.val
|
|
352
|
-
return self.error(f'{self.val} is
|
|
356
|
+
def is_5(self):
|
|
357
|
+
if self.val != 5:
|
|
358
|
+
return self.error(f'{self.val} is NOT 5!')
|
|
353
359
|
return self
|
|
354
360
|
|
|
355
|
-
add_extension(
|
|
361
|
+
add_extension(is_5)
|
|
356
362
|
|
|
357
|
-
assert_that(
|
|
363
|
+
assert_that(5).is_5()
|
|
358
364
|
```
|
|
359
365
|
|
|
360
366
|
See the [full API reference](docs/api.md) for all assertion methods, examples, and advanced features.
|
|
361
367
|
|
|
362
368
|
|
|
369
|
+
## Allure integration
|
|
370
|
+
|
|
371
|
+
When `allure-pytest` is installed, the pytest plugin auto-attaches structured failure data to Allure reports as JSON attachments.
|
|
372
|
+
|
|
373
|
+
```bash
|
|
374
|
+
pip install assertpy2[allure]
|
|
375
|
+
```
|
|
376
|
+
|
|
377
|
+
Three modes controlled via `pytest.ini` (or `pyproject.toml`):
|
|
378
|
+
|
|
379
|
+
| Mode | What is attached |
|
|
380
|
+
|---|---|
|
|
381
|
+
| `diff` (default) | Structured Diff JSON (path-level breakdown) |
|
|
382
|
+
| `full` | Structured Diff + actual/expected JSON |
|
|
383
|
+
| `off` | Nothing |
|
|
384
|
+
|
|
385
|
+
```toml
|
|
386
|
+
# pyproject.toml
|
|
387
|
+
[tool.pytest.ini_options]
|
|
388
|
+
assertpy2_allure = "full"
|
|
389
|
+
```
|
|
390
|
+
|
|
391
|
+
|
|
392
|
+
## Behave step matchers
|
|
393
|
+
|
|
394
|
+
Ready-made parameter types for Behave step definitions:
|
|
395
|
+
|
|
396
|
+
```bash
|
|
397
|
+
pip install assertpy2[behave]
|
|
398
|
+
```
|
|
399
|
+
|
|
400
|
+
```py
|
|
401
|
+
# in environment.py or steps/conftest.py
|
|
402
|
+
from assertpy2.behave_matchers import register_assertpy_types
|
|
403
|
+
register_assertpy_types()
|
|
404
|
+
```
|
|
405
|
+
|
|
406
|
+
Then use in step definitions:
|
|
407
|
+
|
|
408
|
+
```py
|
|
409
|
+
@given('a user aged {age:PositiveInt}')
|
|
410
|
+
def step_impl(context, age):
|
|
411
|
+
context.age = age # already validated as int > 0
|
|
412
|
+
```
|
|
413
|
+
|
|
414
|
+
Available types: `PositiveInt`, `NonNegativeInt`, `PositiveFloat`, `NonEmptyString`, `BoolLike`.
|
|
415
|
+
|
|
416
|
+
|
|
363
417
|
## Migration from assertpy
|
|
364
418
|
|
|
365
419
|
assertpy2 is a drop-in replacement for Python 3.10+. Change the import, everything else works:
|
|
@@ -95,7 +95,7 @@ FAILED test_example.py::test_comparison
|
|
|
95
95
|
| **Async assertions** | No | No | No | **eventually() with polling** |
|
|
96
96
|
| **Soft assertions** | No | No | Yes (not thread-safe) | **Yes (thread-safe, async-safe)** |
|
|
97
97
|
| **Structured errors** | Rewrite only | Mismatch string | String only | **.actual .expected .diff** |
|
|
98
|
-
| **Maintained** | N/A | Minimal | 2020 | **Active
|
|
98
|
+
| **Maintained** | N/A | Minimal | 2020 | **Active** |
|
|
99
99
|
|
|
100
100
|
</div>
|
|
101
101
|
|
|
@@ -136,6 +136,8 @@ assert_that(items).is_type_of(list).is_length(3).contains("admin")
|
|
|
136
136
|
- **Extracting**: flatten collections on attributes with `filter` and `sort` support.
|
|
137
137
|
- **File assertions**: `exists()`, `is_file()`, `is_readable()`, `is_writable()`, `is_executable()` with `pathlib.Path` support.
|
|
138
138
|
- **Snapshot testing**: store and compare data structures in JSON format, inspired by Jest.
|
|
139
|
+
- **Allure integration**: auto-attach structured diff and actual/expected data to Allure reports.
|
|
140
|
+
- **Behave step matchers**: ready-made parameter types (`PositiveInt`, `BoolLike`, etc.) for Behave step definitions.
|
|
139
141
|
- **Extensions**: add custom assertions via `add_extension()`.
|
|
140
142
|
- Strings, numbers, lists, tuples, sets, dicts, dates, booleans, objects, exceptions.
|
|
141
143
|
|
|
@@ -163,7 +165,7 @@ assert_that("hello").satisfies(~match.equal_to("world"))
|
|
|
163
165
|
assert_that(150).satisfies(match.is_negative() | match.greater_than(100))
|
|
164
166
|
```
|
|
165
167
|
|
|
166
|
-
Available matchers: `equal_to`, `greater_than`, `greater_than_or_equal_to`, `less_than`, `less_than_or_equal_to`, `between`, `close_to`, `is_none`, `is_not_none`, `is_instance_of`, `has_length`, `is_empty`, `is_not_empty`, `is_positive`, `is_negative`, `contains_string`, `matches_regex`, `is_uuid`, `is_non_empty_string`, `ignore`, `each_item`, `structure`.
|
|
168
|
+
Available matchers: `equal_to`, `greater_than`, `greater_than_or_equal_to`, `less_than`, `less_than_or_equal_to`, `between`, `close_to`, `is_none`, `is_not_none`, `is_instance_of`, `has_length`, `is_empty`, `is_not_empty`, `is_positive`, `is_negative`, `is_zero`, `is_even`, `is_odd`, `is_divisible_by`, `is_callable`, `is_in`, `has_property`, `contains_string`, `matches_regex`, `is_uuid`, `is_non_empty_string`, `ignore`, `each_item`, `structure`.
|
|
167
169
|
|
|
168
170
|
|
|
169
171
|
## Structural matching
|
|
@@ -316,19 +318,67 @@ assert_that({"a": 1, "b": 2, "c": 3}).snapshot()
|
|
|
316
318
|
```py
|
|
317
319
|
from assertpy2 import add_extension
|
|
318
320
|
|
|
319
|
-
def
|
|
320
|
-
if self.val
|
|
321
|
-
return self.error(f'{self.val} is
|
|
321
|
+
def is_5(self):
|
|
322
|
+
if self.val != 5:
|
|
323
|
+
return self.error(f'{self.val} is NOT 5!')
|
|
322
324
|
return self
|
|
323
325
|
|
|
324
|
-
add_extension(
|
|
326
|
+
add_extension(is_5)
|
|
325
327
|
|
|
326
|
-
assert_that(
|
|
328
|
+
assert_that(5).is_5()
|
|
327
329
|
```
|
|
328
330
|
|
|
329
331
|
See the [full API reference](docs/api.md) for all assertion methods, examples, and advanced features.
|
|
330
332
|
|
|
331
333
|
|
|
334
|
+
## Allure integration
|
|
335
|
+
|
|
336
|
+
When `allure-pytest` is installed, the pytest plugin auto-attaches structured failure data to Allure reports as JSON attachments.
|
|
337
|
+
|
|
338
|
+
```bash
|
|
339
|
+
pip install assertpy2[allure]
|
|
340
|
+
```
|
|
341
|
+
|
|
342
|
+
Three modes controlled via `pytest.ini` (or `pyproject.toml`):
|
|
343
|
+
|
|
344
|
+
| Mode | What is attached |
|
|
345
|
+
|---|---|
|
|
346
|
+
| `diff` (default) | Structured Diff JSON (path-level breakdown) |
|
|
347
|
+
| `full` | Structured Diff + actual/expected JSON |
|
|
348
|
+
| `off` | Nothing |
|
|
349
|
+
|
|
350
|
+
```toml
|
|
351
|
+
# pyproject.toml
|
|
352
|
+
[tool.pytest.ini_options]
|
|
353
|
+
assertpy2_allure = "full"
|
|
354
|
+
```
|
|
355
|
+
|
|
356
|
+
|
|
357
|
+
## Behave step matchers
|
|
358
|
+
|
|
359
|
+
Ready-made parameter types for Behave step definitions:
|
|
360
|
+
|
|
361
|
+
```bash
|
|
362
|
+
pip install assertpy2[behave]
|
|
363
|
+
```
|
|
364
|
+
|
|
365
|
+
```py
|
|
366
|
+
# in environment.py or steps/conftest.py
|
|
367
|
+
from assertpy2.behave_matchers import register_assertpy_types
|
|
368
|
+
register_assertpy_types()
|
|
369
|
+
```
|
|
370
|
+
|
|
371
|
+
Then use in step definitions:
|
|
372
|
+
|
|
373
|
+
```py
|
|
374
|
+
@given('a user aged {age:PositiveInt}')
|
|
375
|
+
def step_impl(context, age):
|
|
376
|
+
context.age = age # already validated as int > 0
|
|
377
|
+
```
|
|
378
|
+
|
|
379
|
+
Available types: `PositiveInt`, `NonNegativeInt`, `PositiveFloat`, `NonEmptyString`, `BoolLike`.
|
|
380
|
+
|
|
381
|
+
|
|
332
382
|
## Migration from assertpy
|
|
333
383
|
|
|
334
384
|
assertpy2 is a drop-in replacement for Python 3.10+. Change the import, everything else works:
|
|
@@ -27,6 +27,8 @@ if TYPE_CHECKING:
|
|
|
27
27
|
def is_type_of(self, some_type: type) -> Self: ...
|
|
28
28
|
def is_instance_of(self, some_class: type) -> Self: ...
|
|
29
29
|
def is_length(self, length: int) -> Self: ...
|
|
30
|
+
def is_callable(self) -> Self: ...
|
|
31
|
+
def is_not_callable(self) -> Self: ...
|
|
30
32
|
def satisfies(self, matcher: Matcher | Callable[..., bool]) -> Self: ...
|
|
31
33
|
# ContainsMixin - universal
|
|
32
34
|
def is_in(self, *items: object) -> Self: ...
|
|
@@ -48,6 +50,10 @@ if TYPE_CHECKING:
|
|
|
48
50
|
def is_digit(self) -> Self: ...
|
|
49
51
|
def is_lower(self) -> Self: ...
|
|
50
52
|
def is_upper(self) -> Self: ...
|
|
53
|
+
def is_alphanumeric(self) -> Self: ...
|
|
54
|
+
def is_whitespace(self) -> Self: ...
|
|
55
|
+
def contains_any_of(self, *items: str) -> Self: ...
|
|
56
|
+
def contains_none_of(self, *items: str) -> Self: ...
|
|
51
57
|
def is_unicode(self) -> Self: ...
|
|
52
58
|
# ContainsMixin
|
|
53
59
|
def contains(self, *items: object) -> Self: ...
|
|
@@ -56,6 +62,8 @@ if TYPE_CHECKING:
|
|
|
56
62
|
def contains_sequence(self, *items: object) -> Self: ...
|
|
57
63
|
def contains_duplicates(self) -> Self: ...
|
|
58
64
|
def does_not_contain_duplicates(self) -> Self: ...
|
|
65
|
+
def contains_exactly(self, *items: object) -> Self: ...
|
|
66
|
+
def contains_in_order(self, *items: object) -> Self: ...
|
|
59
67
|
def is_empty(self) -> Self: ...
|
|
60
68
|
def is_not_empty(self) -> Self: ...
|
|
61
69
|
|
|
@@ -74,6 +82,9 @@ if TYPE_CHECKING:
|
|
|
74
82
|
def is_less_than_or_equal_to(self, other: object) -> Self: ...
|
|
75
83
|
def is_positive(self) -> Self: ...
|
|
76
84
|
def is_negative(self) -> Self: ...
|
|
85
|
+
def is_even(self) -> Self: ...
|
|
86
|
+
def is_odd(self) -> Self: ...
|
|
87
|
+
def is_divisible_by(self, divisor: int) -> Self: ...
|
|
77
88
|
def is_between(self, low: object, high: object) -> Self: ...
|
|
78
89
|
def is_not_between(self, low: object, high: object) -> Self: ...
|
|
79
90
|
def is_close_to(self, other: object, tolerance: object) -> Self: ...
|
|
@@ -89,6 +100,8 @@ if TYPE_CHECKING:
|
|
|
89
100
|
def contains_sequence(self, *items: object) -> Self: ...
|
|
90
101
|
def contains_duplicates(self) -> Self: ...
|
|
91
102
|
def does_not_contain_duplicates(self) -> Self: ...
|
|
103
|
+
def contains_exactly(self, *items: object) -> Self: ...
|
|
104
|
+
def contains_in_order(self, *items: object) -> Self: ...
|
|
92
105
|
def is_empty(self) -> Self: ...
|
|
93
106
|
def is_not_empty(self) -> Self: ...
|
|
94
107
|
# CollectionMixin
|
|
@@ -100,6 +113,9 @@ if TYPE_CHECKING:
|
|
|
100
113
|
def extracting(self, *names: object, **kwargs: object) -> Self: ...
|
|
101
114
|
# BaseMixin
|
|
102
115
|
def each(self, matcher: Matcher | Callable[..., bool]) -> Self: ...
|
|
116
|
+
def any_satisfy(self, matcher: Matcher | Callable[..., bool]) -> Self: ...
|
|
117
|
+
def all_satisfy(self, matcher: Matcher | Callable[..., bool]) -> Self: ...
|
|
118
|
+
def none_satisfy(self, matcher: Matcher | Callable[..., bool]) -> Self: ...
|
|
103
119
|
|
|
104
120
|
class _DictAssertion(_CoreAssertion, Protocol):
|
|
105
121
|
"""Assertions available for ``dict`` values."""
|
|
@@ -127,6 +143,8 @@ if TYPE_CHECKING:
|
|
|
127
143
|
|
|
128
144
|
def is_before(self, other: object) -> Self: ...
|
|
129
145
|
def is_after(self, other: object) -> Self: ...
|
|
146
|
+
def is_before_or_equal_to(self, other: object) -> Self: ...
|
|
147
|
+
def is_after_or_equal_to(self, other: object) -> Self: ...
|
|
130
148
|
def is_equal_to_ignoring_milliseconds(self, other: object) -> Self: ...
|
|
131
149
|
def is_equal_to_ignoring_seconds(self, other: object) -> Self: ...
|
|
132
150
|
def is_equal_to_ignoring_time(self, other: object) -> Self: ...
|
|
@@ -148,5 +166,6 @@ if TYPE_CHECKING:
|
|
|
148
166
|
"""Assertions available for callable values."""
|
|
149
167
|
|
|
150
168
|
def raises(self, ex: type) -> Self: ...
|
|
169
|
+
def does_not_raise(self, ex: type) -> Self: ...
|
|
151
170
|
def when_called_with(self, *some_args: object, **some_kwargs: object) -> Self: ...
|
|
152
171
|
def eventually(self, *, timeout: float = ..., interval: float = ...) -> AsyncAssertionBuilder: ...
|
|
@@ -160,7 +160,7 @@ def soft_assertions() -> Iterator[None]:
|
|
|
160
160
|
|
|
161
161
|
errs = _soft_err.get([])
|
|
162
162
|
if errs and _soft_ctx.get() == 0:
|
|
163
|
-
out = "soft assertion failures:\n" + "\n".join("
|
|
163
|
+
out = "soft assertion failures:\n" + "\n".join(f"{i + 1}. {msg}" for i, msg in enumerate(errs))
|
|
164
164
|
_soft_err.set([])
|
|
165
165
|
raise AssertionError(out)
|
|
166
166
|
|
|
@@ -300,7 +300,7 @@ def fail(msg=""):
|
|
|
300
300
|
except TypeError as e:
|
|
301
301
|
assert_that(str(e)).contains('unsupported operand')
|
|
302
302
|
"""
|
|
303
|
-
raise AssertionError("Fail:
|
|
303
|
+
raise AssertionError(f"Fail: {msg}!" if msg else "Fail!")
|
|
304
304
|
|
|
305
305
|
|
|
306
306
|
def soft_fail(msg=""):
|
|
@@ -332,7 +332,7 @@ def soft_fail(msg=""):
|
|
|
332
332
|
|
|
333
333
|
"""
|
|
334
334
|
if _soft_ctx.get():
|
|
335
|
-
_soft_err.get().append("Fail:
|
|
335
|
+
_soft_err.get().append(f"Fail: {msg}!" if msg else "Fail!")
|
|
336
336
|
return
|
|
337
337
|
fail(msg)
|
|
338
338
|
|
|
@@ -427,7 +427,7 @@ class WarningLoggingAdapter(logging.LoggerAdapter):
|
|
|
427
427
|
prev = frame
|
|
428
428
|
|
|
429
429
|
filename, lineno = _unwind(inspect.currentframe())
|
|
430
|
-
return "[
|
|
430
|
+
return f"[{os.path.basename(filename)}:{lineno}]: {msg}", kwargs
|
|
431
431
|
|
|
432
432
|
|
|
433
433
|
_logger = logging.getLogger("assertpy2")
|
|
@@ -512,7 +512,7 @@ class AssertionBuilder(
|
|
|
512
512
|
AssertionBuilder: returns this instance to chain to the next assertion, but only when
|
|
513
513
|
``AssertionError`` is not raised, as is the case when ``kind`` is ``warn`` or ``soft``.
|
|
514
514
|
"""
|
|
515
|
-
out = "
|
|
515
|
+
out = f"{f'[{self.description}] ' if len(self.description) > 0 else ''}{msg}"
|
|
516
516
|
if self.kind == "warn":
|
|
517
517
|
self.logger.warning(out)
|
|
518
518
|
return self
|
|
@@ -73,8 +73,8 @@ class AsyncAssertionBuilder:
|
|
|
73
73
|
last_error = exc
|
|
74
74
|
if loop.time() >= deadline:
|
|
75
75
|
raise AssertionError(
|
|
76
|
-
"Expected condition not met after
|
|
77
|
-
|
|
76
|
+
f"Expected condition not met after {self._timeout:.1f} seconds."
|
|
77
|
+
f" Last failure: {last_error}"
|
|
78
78
|
) from last_error
|
|
79
79
|
await asyncio.sleep(self._interval)
|
|
80
80
|
|