assertpy2 2.3.1__tar.gz → 2.3.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.3.1 → assertpy2-2.3.3}/.github/workflows/ci.yml +3 -0
- {assertpy2-2.3.1 → assertpy2-2.3.3}/PKG-INFO +52 -3
- {assertpy2-2.3.1 → assertpy2-2.3.3}/README.md +50 -1
- {assertpy2-2.3.1 → assertpy2-2.3.3}/assertpy2/__init__.py +3 -1
- assertpy2-2.3.3/assertpy2/_mixin_base.py +89 -0
- {assertpy2-2.3.1 → assertpy2-2.3.3}/assertpy2/assertpy.py +3 -2
- {assertpy2-2.3.1 → assertpy2-2.3.3}/assertpy2/base.py +2 -1
- {assertpy2-2.3.1 → assertpy2-2.3.3}/assertpy2/behave_matchers.py +1 -1
- {assertpy2-2.3.1 → assertpy2-2.3.3}/assertpy2/collection.py +3 -1
- {assertpy2-2.3.1 → assertpy2-2.3.3}/assertpy2/contains.py +2 -1
- {assertpy2-2.3.1 → assertpy2-2.3.3}/assertpy2/date.py +3 -1
- {assertpy2-2.3.1 → assertpy2-2.3.3}/assertpy2/dict.py +3 -1
- {assertpy2-2.3.1 → assertpy2-2.3.3}/assertpy2/dynamic.py +3 -1
- {assertpy2-2.3.1 → assertpy2-2.3.3}/assertpy2/exception.py +8 -5
- {assertpy2-2.3.1 → assertpy2-2.3.3}/assertpy2/extracting.py +3 -1
- {assertpy2-2.3.1 → assertpy2-2.3.3}/assertpy2/file.py +4 -7
- {assertpy2-2.3.1 → assertpy2-2.3.3}/assertpy2/helpers.py +3 -1
- {assertpy2-2.3.1 → assertpy2-2.3.3}/assertpy2/matchers.py +121 -54
- {assertpy2-2.3.1 → assertpy2-2.3.3}/assertpy2/numeric.py +3 -1
- {assertpy2-2.3.1 → assertpy2-2.3.3}/assertpy2/pytest_plugin.py +1 -1
- {assertpy2-2.3.1 → assertpy2-2.3.3}/assertpy2/snapshot.py +8 -3
- {assertpy2-2.3.1 → assertpy2-2.3.3}/assertpy2/string.py +93 -1
- {assertpy2-2.3.1 → assertpy2-2.3.3}/docs/api.md +102 -0
- {assertpy2-2.3.1 → assertpy2-2.3.3}/pyproject.toml +6 -3
- assertpy2-2.3.3/tests/test_matcher_registry.py +155 -0
- assertpy2-2.3.3/tests/test_regex_groups.py +118 -0
- {assertpy2-2.3.1 → assertpy2-2.3.3}/uv.lock +28 -1
- {assertpy2-2.3.1 → assertpy2-2.3.3}/.codecov.yml +0 -0
- {assertpy2-2.3.1 → assertpy2-2.3.3}/.github/dependabot.yml +0 -0
- {assertpy2-2.3.1 → assertpy2-2.3.3}/.github/workflows/codeql.yml +0 -0
- {assertpy2-2.3.1 → assertpy2-2.3.3}/.github/workflows/publish.yml +0 -0
- {assertpy2-2.3.1 → assertpy2-2.3.3}/.github/workflows/scorecard.yml +0 -0
- {assertpy2-2.3.1 → assertpy2-2.3.3}/.gitignore +0 -0
- {assertpy2-2.3.1 → assertpy2-2.3.3}/CONTRIBUTING.md +0 -0
- {assertpy2-2.3.1 → assertpy2-2.3.3}/LICENSE +0 -0
- {assertpy2-2.3.1 → assertpy2-2.3.3}/SECURITY.md +0 -0
- {assertpy2-2.3.1 → assertpy2-2.3.3}/assertpy2/_typing.py +0 -0
- {assertpy2-2.3.1 → assertpy2-2.3.3}/assertpy2/async_assertions.py +0 -0
- {assertpy2-2.3.1 → assertpy2-2.3.3}/assertpy2/errors.py +0 -0
- {assertpy2-2.3.1 → assertpy2-2.3.3}/assertpy2/py.typed +0 -0
- {assertpy2-2.3.1 → assertpy2-2.3.3}/docs/logo-dark.svg +0 -0
- {assertpy2-2.3.1 → assertpy2-2.3.3}/docs/logo.svg +0 -0
- {assertpy2-2.3.1 → assertpy2-2.3.3}/tests/test_async.py +0 -0
- {assertpy2-2.3.1 → assertpy2-2.3.3}/tests/test_behave_matchers.py +0 -0
- {assertpy2-2.3.1 → assertpy2-2.3.3}/tests/test_bool.py +0 -0
- {assertpy2-2.3.1 → assertpy2-2.3.3}/tests/test_class.py +0 -0
- {assertpy2-2.3.1 → assertpy2-2.3.3}/tests/test_collection.py +0 -0
- {assertpy2-2.3.1 → assertpy2-2.3.3}/tests/test_core.py +0 -0
- {assertpy2-2.3.1 → assertpy2-2.3.3}/tests/test_custom_dict.py +0 -0
- {assertpy2-2.3.1 → assertpy2-2.3.3}/tests/test_custom_list.py +0 -0
- {assertpy2-2.3.1 → assertpy2-2.3.3}/tests/test_datetime.py +0 -0
- {assertpy2-2.3.1 → assertpy2-2.3.3}/tests/test_description.py +0 -0
- {assertpy2-2.3.1 → assertpy2-2.3.3}/tests/test_dict.py +0 -0
- {assertpy2-2.3.1 → assertpy2-2.3.3}/tests/test_dict_compare.py +0 -0
- {assertpy2-2.3.1 → assertpy2-2.3.3}/tests/test_dyn.py +0 -0
- {assertpy2-2.3.1 → assertpy2-2.3.3}/tests/test_equals.py +0 -0
- {assertpy2-2.3.1 → assertpy2-2.3.3}/tests/test_errors.py +0 -0
- {assertpy2-2.3.1 → assertpy2-2.3.3}/tests/test_expected_exception.py +0 -0
- {assertpy2-2.3.1 → assertpy2-2.3.3}/tests/test_extensions.py +0 -0
- {assertpy2-2.3.1 → assertpy2-2.3.3}/tests/test_extracting.py +0 -0
- {assertpy2-2.3.1 → assertpy2-2.3.3}/tests/test_fail.py +0 -0
- {assertpy2-2.3.1 → assertpy2-2.3.3}/tests/test_file.py +0 -0
- {assertpy2-2.3.1 → assertpy2-2.3.3}/tests/test_in.py +0 -0
- {assertpy2-2.3.1 → assertpy2-2.3.3}/tests/test_list.py +0 -0
- {assertpy2-2.3.1 → assertpy2-2.3.3}/tests/test_matchers.py +0 -0
- {assertpy2-2.3.1 → assertpy2-2.3.3}/tests/test_matchers_phase3.py +0 -0
- {assertpy2-2.3.1 → assertpy2-2.3.3}/tests/test_namedtuple.py +0 -0
- {assertpy2-2.3.1 → assertpy2-2.3.3}/tests/test_none.py +0 -0
- {assertpy2-2.3.1 → assertpy2-2.3.3}/tests/test_numbers.py +0 -0
- {assertpy2-2.3.1 → assertpy2-2.3.3}/tests/test_overloads.py +0 -0
- {assertpy2-2.3.1 → assertpy2-2.3.3}/tests/test_phase2.py +0 -0
- {assertpy2-2.3.1 → assertpy2-2.3.3}/tests/test_pytest_plugin.py +0 -0
- {assertpy2-2.3.1 → assertpy2-2.3.3}/tests/test_readme.py +0 -0
- {assertpy2-2.3.1 → assertpy2-2.3.3}/tests/test_same_as.py +0 -0
- {assertpy2-2.3.1 → assertpy2-2.3.3}/tests/test_snapshots.py +0 -0
- {assertpy2-2.3.1 → assertpy2-2.3.3}/tests/test_soft.py +0 -0
- {assertpy2-2.3.1 → assertpy2-2.3.3}/tests/test_soft_fail.py +0 -0
- {assertpy2-2.3.1 → assertpy2-2.3.3}/tests/test_string.py +0 -0
- {assertpy2-2.3.1 → assertpy2-2.3.3}/tests/test_structural.py +0 -0
- {assertpy2-2.3.1 → assertpy2-2.3.3}/tests/test_traceback.py +0 -0
- {assertpy2-2.3.1 → assertpy2-2.3.3}/tests/test_type.py +0 -0
- {assertpy2-2.3.1 → assertpy2-2.3.3}/tests/test_warn.py +0 -0
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: assertpy2
|
|
3
|
-
Version: 2.3.
|
|
4
|
-
Summary: Fluent assertion library for Python with
|
|
3
|
+
Version: 2.3.3
|
|
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
|
|
7
7
|
Project-URL: Issues, https://github.com/Solganis/assertpy2/issues
|
|
@@ -52,9 +52,11 @@ Description-Content-Type: text/markdown
|
|
|
52
52
|
<a href="https://pepy.tech/projects/assertpy2"><img src="https://static.pepy.tech/badge/assertpy2/month" alt="Downloads"></a>
|
|
53
53
|
<a href="https://pypi.org/project/assertpy2/"><img src="https://img.shields.io/pypi/pyversions/assertpy2" alt="Python"></a>
|
|
54
54
|
<a href="https://codecov.io/gh/Solganis/assertpy2"><img src="https://codecov.io/gh/Solganis/assertpy2/graph/badge.svg" alt="Coverage"></a>
|
|
55
|
-
<
|
|
55
|
+
<br>
|
|
56
|
+
<a href="https://github.com/Solganis/assertpy2/blob/main/docs/api.md"><img src="https://img.shields.io/badge/Docs-Read%20The%20Docs-black" alt="Documentation"></a>
|
|
56
57
|
<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>
|
|
57
58
|
<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>
|
|
59
|
+
<a href="https://github.com/astral-sh/ty"><img src="https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/astral-sh/ty/main/assets/badge/v0.json" alt="ty"></a>
|
|
58
60
|
<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>
|
|
59
61
|
<a href="https://www.bestpractices.dev/projects/12990"><img src="https://www.bestpractices.dev/projects/12990/badge" alt="OpenSSF Best Practices"></a>
|
|
60
62
|
</p>
|
|
@@ -173,6 +175,8 @@ assert_that(items).is_type_of(list).is_length(3).contains("admin")
|
|
|
173
175
|
- **Snapshot testing**: store and compare data structures in JSON format, inspired by Jest.
|
|
174
176
|
- **Allure integration**: auto-attach structured diff and actual/expected data to Allure reports.
|
|
175
177
|
- **Behave step matchers**: ready-made parameter types (`PositiveInt`, `BoolLike`, etc.) for Behave step definitions.
|
|
178
|
+
- **Custom matchers**: register domain-specific matchers via `register_matcher()`, composable with `&`, `|`, `~`.
|
|
179
|
+
- **Regex group extraction**: `extracting_group()` and `matches_with_groups()` to assert on regex captures fluently.
|
|
176
180
|
- **Extensions**: add custom assertions via `add_extension()`.
|
|
177
181
|
- Strings, numbers, lists, tuples, sets, dicts, dates, booleans, objects, exceptions.
|
|
178
182
|
|
|
@@ -348,6 +352,51 @@ assert_that(fred).has_shoe_size(12)
|
|
|
348
352
|
assert_that({"a": 1, "b": 2, "c": 3}).snapshot()
|
|
349
353
|
```
|
|
350
354
|
|
|
355
|
+
### Custom matchers
|
|
356
|
+
|
|
357
|
+
Register domain-specific matchers on the `match` namespace with `register_matcher()`:
|
|
358
|
+
|
|
359
|
+
```py
|
|
360
|
+
from assertpy2 import assert_that, match, register_matcher
|
|
361
|
+
|
|
362
|
+
@register_matcher("is_valid_email")
|
|
363
|
+
def is_valid_email():
|
|
364
|
+
return match.matches_regex(r"^[\w.-]+@[\w.-]+\.\w+$")
|
|
365
|
+
|
|
366
|
+
# parametrised matchers
|
|
367
|
+
@register_matcher("has_status")
|
|
368
|
+
def has_status(expected: str):
|
|
369
|
+
return match.has_property("status", match.equal_to(expected))
|
|
370
|
+
|
|
371
|
+
# use everywhere matchers are accepted
|
|
372
|
+
assert_that("alice@example.com").satisfies(match.is_valid_email())
|
|
373
|
+
assert_that(users).extracting("email").each(match.is_valid_email())
|
|
374
|
+
assert_that(data).matches_structure({"email": match.is_valid_email()})
|
|
375
|
+
|
|
376
|
+
# composition works automatically
|
|
377
|
+
assert_that(email).satisfies(match.is_valid_email() & match.contains_string("@company.com"))
|
|
378
|
+
```
|
|
379
|
+
|
|
380
|
+
Remove with `unregister_matcher("is_valid_email")`.
|
|
381
|
+
|
|
382
|
+
### Regex group extraction
|
|
383
|
+
|
|
384
|
+
Extract regex groups and continue the fluent chain:
|
|
385
|
+
|
|
386
|
+
```py
|
|
387
|
+
log = "2024-01-15 ERROR status=500 path=/api/users"
|
|
388
|
+
|
|
389
|
+
# extract a positional group
|
|
390
|
+
assert_that(log).extracting_group(r"status=(\d+)", 1).is_equal_to("500")
|
|
391
|
+
|
|
392
|
+
# extract a named group
|
|
393
|
+
assert_that(log).extracting_group(r"(?P<level>\w+) status", "level").is_equal_to("ERROR")
|
|
394
|
+
|
|
395
|
+
# get all groups as a tuple or dict (named groups)
|
|
396
|
+
assert_that("key=value").matches_with_groups(r"(?P<k>\w+)=(?P<v>\w+)") \
|
|
397
|
+
.contains_entry({"k": "key"}).contains_entry({"v": "value"})
|
|
398
|
+
```
|
|
399
|
+
|
|
351
400
|
### Extensions
|
|
352
401
|
|
|
353
402
|
```py
|
|
@@ -17,9 +17,11 @@
|
|
|
17
17
|
<a href="https://pepy.tech/projects/assertpy2"><img src="https://static.pepy.tech/badge/assertpy2/month" alt="Downloads"></a>
|
|
18
18
|
<a href="https://pypi.org/project/assertpy2/"><img src="https://img.shields.io/pypi/pyversions/assertpy2" alt="Python"></a>
|
|
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
|
+
<br>
|
|
21
|
+
<a href="https://github.com/Solganis/assertpy2/blob/main/docs/api.md"><img src="https://img.shields.io/badge/Docs-Read%20The%20Docs-black" alt="Documentation"></a>
|
|
21
22
|
<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
23
|
<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>
|
|
24
|
+
<a href="https://github.com/astral-sh/ty"><img src="https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/astral-sh/ty/main/assets/badge/v0.json" alt="ty"></a>
|
|
23
25
|
<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
26
|
<a href="https://www.bestpractices.dev/projects/12990"><img src="https://www.bestpractices.dev/projects/12990/badge" alt="OpenSSF Best Practices"></a>
|
|
25
27
|
</p>
|
|
@@ -138,6 +140,8 @@ assert_that(items).is_type_of(list).is_length(3).contains("admin")
|
|
|
138
140
|
- **Snapshot testing**: store and compare data structures in JSON format, inspired by Jest.
|
|
139
141
|
- **Allure integration**: auto-attach structured diff and actual/expected data to Allure reports.
|
|
140
142
|
- **Behave step matchers**: ready-made parameter types (`PositiveInt`, `BoolLike`, etc.) for Behave step definitions.
|
|
143
|
+
- **Custom matchers**: register domain-specific matchers via `register_matcher()`, composable with `&`, `|`, `~`.
|
|
144
|
+
- **Regex group extraction**: `extracting_group()` and `matches_with_groups()` to assert on regex captures fluently.
|
|
141
145
|
- **Extensions**: add custom assertions via `add_extension()`.
|
|
142
146
|
- Strings, numbers, lists, tuples, sets, dicts, dates, booleans, objects, exceptions.
|
|
143
147
|
|
|
@@ -313,6 +317,51 @@ assert_that(fred).has_shoe_size(12)
|
|
|
313
317
|
assert_that({"a": 1, "b": 2, "c": 3}).snapshot()
|
|
314
318
|
```
|
|
315
319
|
|
|
320
|
+
### Custom matchers
|
|
321
|
+
|
|
322
|
+
Register domain-specific matchers on the `match` namespace with `register_matcher()`:
|
|
323
|
+
|
|
324
|
+
```py
|
|
325
|
+
from assertpy2 import assert_that, match, register_matcher
|
|
326
|
+
|
|
327
|
+
@register_matcher("is_valid_email")
|
|
328
|
+
def is_valid_email():
|
|
329
|
+
return match.matches_regex(r"^[\w.-]+@[\w.-]+\.\w+$")
|
|
330
|
+
|
|
331
|
+
# parametrised matchers
|
|
332
|
+
@register_matcher("has_status")
|
|
333
|
+
def has_status(expected: str):
|
|
334
|
+
return match.has_property("status", match.equal_to(expected))
|
|
335
|
+
|
|
336
|
+
# use everywhere matchers are accepted
|
|
337
|
+
assert_that("alice@example.com").satisfies(match.is_valid_email())
|
|
338
|
+
assert_that(users).extracting("email").each(match.is_valid_email())
|
|
339
|
+
assert_that(data).matches_structure({"email": match.is_valid_email()})
|
|
340
|
+
|
|
341
|
+
# composition works automatically
|
|
342
|
+
assert_that(email).satisfies(match.is_valid_email() & match.contains_string("@company.com"))
|
|
343
|
+
```
|
|
344
|
+
|
|
345
|
+
Remove with `unregister_matcher("is_valid_email")`.
|
|
346
|
+
|
|
347
|
+
### Regex group extraction
|
|
348
|
+
|
|
349
|
+
Extract regex groups and continue the fluent chain:
|
|
350
|
+
|
|
351
|
+
```py
|
|
352
|
+
log = "2024-01-15 ERROR status=500 path=/api/users"
|
|
353
|
+
|
|
354
|
+
# extract a positional group
|
|
355
|
+
assert_that(log).extracting_group(r"status=(\d+)", 1).is_equal_to("500")
|
|
356
|
+
|
|
357
|
+
# extract a named group
|
|
358
|
+
assert_that(log).extracting_group(r"(?P<level>\w+) status", "level").is_equal_to("ERROR")
|
|
359
|
+
|
|
360
|
+
# get all groups as a tuple or dict (named groups)
|
|
361
|
+
assert_that("key=value").matches_with_groups(r"(?P<k>\w+)=(?P<v>\w+)") \
|
|
362
|
+
.contains_entry({"k": "key"}).contains_entry({"v": "value"})
|
|
363
|
+
```
|
|
364
|
+
|
|
316
365
|
### Extensions
|
|
317
366
|
|
|
318
367
|
```py
|
|
@@ -12,7 +12,7 @@ from .assertpy import (
|
|
|
12
12
|
from .async_assertions import AsyncAssertionBuilder
|
|
13
13
|
from .errors import AssertionFailure, DiffEntry, DiffResult
|
|
14
14
|
from .file import contents_of
|
|
15
|
-
from .matchers import Matcher, match
|
|
15
|
+
from .matchers import Matcher, match, register_matcher, unregister_matcher
|
|
16
16
|
|
|
17
17
|
__all__ = [
|
|
18
18
|
"AssertionFailure",
|
|
@@ -28,7 +28,9 @@ __all__ = [
|
|
|
28
28
|
"contents_of",
|
|
29
29
|
"fail",
|
|
30
30
|
"match",
|
|
31
|
+
"register_matcher",
|
|
31
32
|
"remove_extension",
|
|
32
33
|
"soft_assertions",
|
|
33
34
|
"soft_fail",
|
|
35
|
+
"unregister_matcher",
|
|
34
36
|
]
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import logging
|
|
4
|
+
from typing import TYPE_CHECKING, Any
|
|
5
|
+
|
|
6
|
+
if TYPE_CHECKING:
|
|
7
|
+
from typing_extensions import Self
|
|
8
|
+
|
|
9
|
+
from .errors import DiffResult
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class _MixinBase:
|
|
13
|
+
if TYPE_CHECKING:
|
|
14
|
+
val: Any
|
|
15
|
+
description: str
|
|
16
|
+
kind: str | None
|
|
17
|
+
expected: type[BaseException] | None
|
|
18
|
+
logger: logging.LoggerAdapter
|
|
19
|
+
_not_expected: bool
|
|
20
|
+
|
|
21
|
+
def error(
|
|
22
|
+
self,
|
|
23
|
+
msg: str,
|
|
24
|
+
*,
|
|
25
|
+
actual: object = ...,
|
|
26
|
+
expected: object = ...,
|
|
27
|
+
diff: DiffResult | None = ...,
|
|
28
|
+
) -> Self: ...
|
|
29
|
+
|
|
30
|
+
def builder(
|
|
31
|
+
self,
|
|
32
|
+
val: object,
|
|
33
|
+
description: str = ...,
|
|
34
|
+
kind: str | None = ...,
|
|
35
|
+
expected: BaseException | None = ...,
|
|
36
|
+
logger: logging.LoggerAdapter | None = ...,
|
|
37
|
+
) -> Self: ...
|
|
38
|
+
|
|
39
|
+
# HelpersMixin
|
|
40
|
+
|
|
41
|
+
def _fmt_items(self, i: object) -> str: ...
|
|
42
|
+
|
|
43
|
+
def _fmt_args_kwargs(self, *some_args: object, **some_kwargs: object) -> str: ...
|
|
44
|
+
|
|
45
|
+
def _validate_between_args(self, val_type: type, low: object, high: object) -> None: ...
|
|
46
|
+
|
|
47
|
+
def _validate_close_to_args(self, val: object, other: object, tolerance: object) -> None: ...
|
|
48
|
+
|
|
49
|
+
def _check_dict_like(
|
|
50
|
+
self,
|
|
51
|
+
d: object,
|
|
52
|
+
check_keys: bool = ...,
|
|
53
|
+
check_values: bool = ...,
|
|
54
|
+
check_getitem: bool = ...,
|
|
55
|
+
name: str = ...,
|
|
56
|
+
return_as_bool: bool = ...,
|
|
57
|
+
) -> bool | None: ...
|
|
58
|
+
|
|
59
|
+
def _check_iterable(self, val: object, check_getitem: bool = ..., name: str = ...) -> None: ...
|
|
60
|
+
|
|
61
|
+
def _dict_not_equal(
|
|
62
|
+
self,
|
|
63
|
+
val: object,
|
|
64
|
+
other: object,
|
|
65
|
+
ignore: object = ...,
|
|
66
|
+
include: object = ...,
|
|
67
|
+
) -> bool: ...
|
|
68
|
+
|
|
69
|
+
def _dict_err(
|
|
70
|
+
self,
|
|
71
|
+
val: object,
|
|
72
|
+
other: object,
|
|
73
|
+
ignore: object = ...,
|
|
74
|
+
include: object = ...,
|
|
75
|
+
) -> None: ...
|
|
76
|
+
|
|
77
|
+
# NumericMixin class attrs used by HelpersMixin._validate_between_args
|
|
78
|
+
_NUMERIC_COMPAREABLE: frozenset[type]
|
|
79
|
+
_NUMERIC_NON_COMPAREABLE: frozenset[type]
|
|
80
|
+
|
|
81
|
+
# Cross-mixin methods
|
|
82
|
+
|
|
83
|
+
def contains(self, *items: object) -> Self: ...
|
|
84
|
+
|
|
85
|
+
def does_not_contain(self, *items: object) -> Self: ...
|
|
86
|
+
|
|
87
|
+
def is_equal_to(self, other: object, **kwargs: object) -> Self: ...
|
|
88
|
+
|
|
89
|
+
def is_not_equal_to(self, other: object) -> Self: ...
|
|
@@ -73,10 +73,10 @@ from .numeric import NumericMixin
|
|
|
73
73
|
from .snapshot import SnapshotMixin
|
|
74
74
|
from .string import StringMixin
|
|
75
75
|
|
|
76
|
-
__version__ = "2.3.
|
|
76
|
+
__version__ = "2.3.3"
|
|
77
77
|
|
|
78
78
|
__tracebackhide__ = True # clean tracebacks via py.test integration
|
|
79
|
-
contextlib.__tracebackhide__ = True #
|
|
79
|
+
contextlib.__tracebackhide__ = True # ty: ignore[unresolved-attribute] # pytest monkey-patch
|
|
80
80
|
|
|
81
81
|
# assertpy files
|
|
82
82
|
ASSERTPY_FILES = [
|
|
@@ -475,6 +475,7 @@ class AssertionBuilder(
|
|
|
475
475
|
self.kind = kind
|
|
476
476
|
self.expected = expected
|
|
477
477
|
self.logger = logger if logger else _default_logger
|
|
478
|
+
self._not_expected = False
|
|
478
479
|
|
|
479
480
|
def builder(self, val, description="", kind=None, expected=None, logger=None):
|
|
480
481
|
"""Helper to build a new :class:`AssertionBuilder` instance. Use this only if not chaining to ``self``.
|
|
@@ -31,6 +31,7 @@ from __future__ import annotations
|
|
|
31
31
|
import collections.abc
|
|
32
32
|
from typing import TYPE_CHECKING
|
|
33
33
|
|
|
34
|
+
from ._mixin_base import _MixinBase
|
|
34
35
|
from .errors import DiffEntry, DiffResult
|
|
35
36
|
from .matchers import Matcher, StructureMatcher
|
|
36
37
|
|
|
@@ -40,7 +41,7 @@ if TYPE_CHECKING:
|
|
|
40
41
|
__tracebackhide__ = True
|
|
41
42
|
|
|
42
43
|
|
|
43
|
-
class BaseMixin:
|
|
44
|
+
class BaseMixin(_MixinBase):
|
|
44
45
|
"""Base mixin."""
|
|
45
46
|
|
|
46
47
|
def described_as(self, description) -> Self:
|
|
@@ -77,7 +77,7 @@ def register_assertpy_types() -> None:
|
|
|
77
77
|
ImportError: if behave is not installed
|
|
78
78
|
"""
|
|
79
79
|
try:
|
|
80
|
-
from behave import register_type
|
|
80
|
+
from behave import register_type # ty: ignore[unresolved-import] # optional dependency
|
|
81
81
|
except ImportError:
|
|
82
82
|
raise ImportError(
|
|
83
83
|
"behave is required for register_assertpy_types(). Install it with: pip install assertpy2[behave]"
|
|
@@ -31,13 +31,15 @@ from __future__ import annotations
|
|
|
31
31
|
import collections
|
|
32
32
|
from typing import TYPE_CHECKING
|
|
33
33
|
|
|
34
|
+
from ._mixin_base import _MixinBase
|
|
35
|
+
|
|
34
36
|
if TYPE_CHECKING:
|
|
35
37
|
from typing_extensions import Self
|
|
36
38
|
|
|
37
39
|
__tracebackhide__ = True
|
|
38
40
|
|
|
39
41
|
|
|
40
|
-
class CollectionMixin:
|
|
42
|
+
class CollectionMixin(_MixinBase):
|
|
41
43
|
"""Collection assertions mixin."""
|
|
42
44
|
|
|
43
45
|
def is_iterable(self) -> Self:
|
|
@@ -31,6 +31,7 @@ from __future__ import annotations
|
|
|
31
31
|
|
|
32
32
|
from typing import TYPE_CHECKING
|
|
33
33
|
|
|
34
|
+
from ._mixin_base import _MixinBase
|
|
34
35
|
from .matchers import Matcher
|
|
35
36
|
|
|
36
37
|
if TYPE_CHECKING:
|
|
@@ -39,7 +40,7 @@ if TYPE_CHECKING:
|
|
|
39
40
|
__tracebackhide__ = True
|
|
40
41
|
|
|
41
42
|
|
|
42
|
-
class ContainsMixin:
|
|
43
|
+
class ContainsMixin(_MixinBase):
|
|
43
44
|
"""Containment assertions mixin."""
|
|
44
45
|
|
|
45
46
|
def contains(self, *items) -> Self:
|
|
@@ -31,13 +31,15 @@ from __future__ import annotations
|
|
|
31
31
|
import datetime
|
|
32
32
|
from typing import TYPE_CHECKING
|
|
33
33
|
|
|
34
|
+
from ._mixin_base import _MixinBase
|
|
35
|
+
|
|
34
36
|
if TYPE_CHECKING:
|
|
35
37
|
from typing_extensions import Self
|
|
36
38
|
|
|
37
39
|
__tracebackhide__ = True
|
|
38
40
|
|
|
39
41
|
|
|
40
|
-
class DateMixin:
|
|
42
|
+
class DateMixin(_MixinBase):
|
|
41
43
|
"""Date and time assertions mixin."""
|
|
42
44
|
|
|
43
45
|
def is_before(self, other) -> Self:
|
|
@@ -30,13 +30,15 @@ from __future__ import annotations
|
|
|
30
30
|
|
|
31
31
|
from typing import TYPE_CHECKING
|
|
32
32
|
|
|
33
|
+
from ._mixin_base import _MixinBase
|
|
34
|
+
|
|
33
35
|
if TYPE_CHECKING:
|
|
34
36
|
from typing_extensions import Self
|
|
35
37
|
|
|
36
38
|
__tracebackhide__ = True
|
|
37
39
|
|
|
38
40
|
|
|
39
|
-
class DictMixin:
|
|
41
|
+
class DictMixin(_MixinBase):
|
|
40
42
|
"""Dict assertions mixin."""
|
|
41
43
|
|
|
42
44
|
def contains_key(self, *keys) -> Self:
|
|
@@ -30,10 +30,12 @@ from __future__ import annotations
|
|
|
30
30
|
|
|
31
31
|
import collections
|
|
32
32
|
|
|
33
|
+
from ._mixin_base import _MixinBase
|
|
34
|
+
|
|
33
35
|
__tracebackhide__ = True
|
|
34
36
|
|
|
35
37
|
|
|
36
|
-
class DynamicMixin:
|
|
38
|
+
class DynamicMixin(_MixinBase):
|
|
37
39
|
"""Dynamic assertions mixin.
|
|
38
40
|
|
|
39
41
|
When testing attributes of an object (or the contents of a dict), the
|
|
@@ -28,7 +28,9 @@
|
|
|
28
28
|
|
|
29
29
|
from __future__ import annotations
|
|
30
30
|
|
|
31
|
-
from typing import TYPE_CHECKING
|
|
31
|
+
from typing import TYPE_CHECKING, cast
|
|
32
|
+
|
|
33
|
+
from ._mixin_base import _MixinBase
|
|
32
34
|
|
|
33
35
|
if TYPE_CHECKING:
|
|
34
36
|
from typing_extensions import Self
|
|
@@ -46,7 +48,7 @@ class _InertBuilder:
|
|
|
46
48
|
return lambda *args, **kwargs: self
|
|
47
49
|
|
|
48
50
|
|
|
49
|
-
class ExceptionMixin:
|
|
51
|
+
class ExceptionMixin(_MixinBase):
|
|
50
52
|
"""Expected exception mixin."""
|
|
51
53
|
|
|
52
54
|
def raises(self, ex) -> Self:
|
|
@@ -116,15 +118,16 @@ class ExceptionMixin:
|
|
|
116
118
|
f" when called with ({self._fmt_args_kwargs(*some_args, **some_kwargs)}),"
|
|
117
119
|
f" but raised <{type(e).__name__}>."
|
|
118
120
|
)
|
|
119
|
-
return _InertBuilder()
|
|
121
|
+
return cast("Self", _InertBuilder())
|
|
120
122
|
|
|
121
123
|
self.error(
|
|
122
124
|
f"Expected <{self.val.__name__}> to raise <{self.expected.__name__}>"
|
|
123
125
|
f" when called with ({self._fmt_args_kwargs(*some_args, **some_kwargs)})."
|
|
124
126
|
)
|
|
125
|
-
return _InertBuilder()
|
|
127
|
+
return cast("Self", _InertBuilder())
|
|
126
128
|
|
|
127
129
|
def _when_called_with_not_expected(self, *some_args, **some_kwargs) -> Self:
|
|
130
|
+
assert self.expected is not None
|
|
128
131
|
try:
|
|
129
132
|
self.val(*some_args, **some_kwargs)
|
|
130
133
|
except BaseException as e:
|
|
@@ -134,7 +137,7 @@ class ExceptionMixin:
|
|
|
134
137
|
f" when called with ({self._fmt_args_kwargs(*some_args, **some_kwargs)}),"
|
|
135
138
|
f" but did raise <{type(e).__name__}>."
|
|
136
139
|
)
|
|
137
|
-
return _InertBuilder()
|
|
140
|
+
return cast("Self", _InertBuilder())
|
|
138
141
|
return self
|
|
139
142
|
|
|
140
143
|
def does_not_raise(self, ex) -> Self:
|
|
@@ -31,13 +31,15 @@ from __future__ import annotations
|
|
|
31
31
|
import collections.abc
|
|
32
32
|
from typing import TYPE_CHECKING
|
|
33
33
|
|
|
34
|
+
from ._mixin_base import _MixinBase
|
|
35
|
+
|
|
34
36
|
if TYPE_CHECKING:
|
|
35
37
|
from typing_extensions import Self
|
|
36
38
|
|
|
37
39
|
__tracebackhide__ = True
|
|
38
40
|
|
|
39
41
|
|
|
40
|
-
class ExtractingMixin:
|
|
42
|
+
class ExtractingMixin(_MixinBase):
|
|
41
43
|
"""Collection flattening mixin.
|
|
42
44
|
|
|
43
45
|
It is often necessary to test collections of objects. Use the ``extracting()`` helper to
|
|
@@ -31,6 +31,8 @@ from __future__ import annotations
|
|
|
31
31
|
import os
|
|
32
32
|
from typing import TYPE_CHECKING
|
|
33
33
|
|
|
34
|
+
from ._mixin_base import _MixinBase
|
|
35
|
+
|
|
34
36
|
if TYPE_CHECKING:
|
|
35
37
|
from typing_extensions import Self
|
|
36
38
|
|
|
@@ -72,17 +74,12 @@ def contents_of(file, encoding="utf-8"):
|
|
|
72
74
|
raise ValueError(f"val must be file or path, but was type <{type(file).__name__}>") from None
|
|
73
75
|
raise
|
|
74
76
|
|
|
75
|
-
if
|
|
76
|
-
return contents.decode(encoding, "replace")
|
|
77
|
-
try:
|
|
77
|
+
if isinstance(contents, (bytes, bytearray)):
|
|
78
78
|
return contents.decode(encoding, "replace")
|
|
79
|
-
except AttributeError:
|
|
80
|
-
pass # contents is already a str, no decode needed
|
|
81
|
-
# if all else fails, just return the contents "as is"
|
|
82
79
|
return contents
|
|
83
80
|
|
|
84
81
|
|
|
85
|
-
class FileMixin:
|
|
82
|
+
class FileMixin(_MixinBase):
|
|
86
83
|
"""File assertions mixin."""
|
|
87
84
|
|
|
88
85
|
def exists(self) -> Self:
|
|
@@ -33,10 +33,12 @@ import numbers
|
|
|
33
33
|
|
|
34
34
|
from assertpy2.errors import DiffEntry, DiffResult
|
|
35
35
|
|
|
36
|
+
from ._mixin_base import _MixinBase
|
|
37
|
+
|
|
36
38
|
__tracebackhide__ = True
|
|
37
39
|
|
|
38
40
|
|
|
39
|
-
class HelpersMixin:
|
|
41
|
+
class HelpersMixin(_MixinBase):
|
|
40
42
|
"""Helpers mixin. For internal use only."""
|
|
41
43
|
|
|
42
44
|
def _fmt_items(self, i):
|