assertpy2 2.5.0__tar.gz → 2.5.1__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.5.1/.github/workflows/docs.yml +57 -0
- {assertpy2-2.5.0 → assertpy2-2.5.1}/.gitignore +1 -0
- {assertpy2-2.5.0 → assertpy2-2.5.1}/PKG-INFO +25 -25
- {assertpy2-2.5.0 → assertpy2-2.5.1}/README.md +23 -23
- assertpy2-2.5.1/assertpy2/_compat.py +15 -0
- {assertpy2-2.5.0 → assertpy2-2.5.1}/assertpy2/_mixin_base.py +1 -2
- {assertpy2-2.5.0 → assertpy2-2.5.1}/assertpy2/_typing.py +1 -2
- {assertpy2-2.5.0 → assertpy2-2.5.1}/assertpy2/assertpy.py +2 -3
- {assertpy2-2.5.0 → assertpy2-2.5.1}/assertpy2/async_assertions.py +1 -1
- {assertpy2-2.5.0 → assertpy2-2.5.1}/assertpy2/base.py +1 -1
- {assertpy2-2.5.0 → assertpy2-2.5.1}/assertpy2/bytes_mixin.py +1 -1
- {assertpy2-2.5.0 → assertpy2-2.5.1}/assertpy2/collection.py +1 -1
- {assertpy2-2.5.0 → assertpy2-2.5.1}/assertpy2/contains.py +1 -1
- {assertpy2-2.5.0 → assertpy2-2.5.1}/assertpy2/date.py +1 -1
- {assertpy2-2.5.0 → assertpy2-2.5.1}/assertpy2/dict.py +1 -1
- {assertpy2-2.5.0 → assertpy2-2.5.1}/assertpy2/exception.py +1 -1
- {assertpy2-2.5.0 → assertpy2-2.5.1}/assertpy2/extracting.py +1 -1
- {assertpy2-2.5.0 → assertpy2-2.5.1}/assertpy2/file.py +1 -1
- {assertpy2-2.5.0 → assertpy2-2.5.1}/assertpy2/json_mixin.py +1 -1
- {assertpy2-2.5.0 → assertpy2-2.5.1}/assertpy2/matchers.py +68 -0
- {assertpy2-2.5.0 → assertpy2-2.5.1}/assertpy2/numeric.py +1 -1
- {assertpy2-2.5.0 → assertpy2-2.5.1}/assertpy2/snapshot.py +1 -1
- {assertpy2-2.5.0 → assertpy2-2.5.1}/assertpy2/string.py +1 -1
- assertpy2-2.5.1/docs/assertions.md +377 -0
- assertpy2-2.5.1/docs/comparison.md +170 -0
- assertpy2-2.5.1/docs/data.md +84 -0
- assertpy2-2.5.1/docs/errors.md +133 -0
- assertpy2-2.5.1/docs/extending.md +72 -0
- assertpy2-2.5.1/docs/fluent.md +104 -0
- assertpy2-2.5.1/docs/getting-started.md +55 -0
- assertpy2-2.5.1/docs/index.md +41 -0
- assertpy2-2.5.1/docs/integrations.md +120 -0
- assertpy2-2.5.1/docs/matchers.md +214 -0
- assertpy2-2.5.1/docs/migration.md +101 -0
- assertpy2-2.5.1/docs/testing.md +119 -0
- assertpy2-2.5.1/mkdocs.yml +81 -0
- {assertpy2-2.5.0 → assertpy2-2.5.1}/pyproject.toml +10 -6
- {assertpy2-2.5.0 → assertpy2-2.5.1}/tests/test_bool.py +7 -9
- {assertpy2-2.5.0 → assertpy2-2.5.1}/tests/test_class.py +7 -9
- {assertpy2-2.5.0 → assertpy2-2.5.1}/tests/test_collection.py +40 -70
- {assertpy2-2.5.0 → assertpy2-2.5.1}/tests/test_custom_dict.py +11 -21
- {assertpy2-2.5.0 → assertpy2-2.5.1}/tests/test_custom_list.py +7 -9
- assertpy2-2.5.1/tests/test_datetime.py +440 -0
- assertpy2-2.5.1/tests/test_description.py +29 -0
- {assertpy2-2.5.0 → assertpy2-2.5.1}/tests/test_dict.py +95 -173
- {assertpy2-2.5.0 → assertpy2-2.5.1}/tests/test_dict_compare.py +107 -163
- {assertpy2-2.5.0 → assertpy2-2.5.1}/tests/test_dyn.py +17 -25
- {assertpy2-2.5.0 → assertpy2-2.5.1}/tests/test_equals.py +15 -25
- {assertpy2-2.5.0 → assertpy2-2.5.1}/tests/test_expected_exception.py +33 -45
- {assertpy2-2.5.0 → assertpy2-2.5.1}/tests/test_extensions.py +29 -53
- {assertpy2-2.5.0 → assertpy2-2.5.1}/tests/test_extracting.py +59 -103
- assertpy2-2.5.1/tests/test_fail.py +15 -0
- {assertpy2-2.5.0 → assertpy2-2.5.1}/tests/test_file.py +45 -89
- {assertpy2-2.5.0 → assertpy2-2.5.1}/tests/test_in.py +11 -17
- {assertpy2-2.5.0 → assertpy2-2.5.1}/tests/test_list.py +79 -141
- {assertpy2-2.5.0 → assertpy2-2.5.1}/tests/test_namedtuple.py +11 -15
- {assertpy2-2.5.0 → assertpy2-2.5.1}/tests/test_none.py +7 -9
- assertpy2-2.5.1/tests/test_numbers.py +643 -0
- {assertpy2-2.5.0 → assertpy2-2.5.1}/tests/test_phase2.py +83 -156
- {assertpy2-2.5.0 → assertpy2-2.5.1}/tests/test_readme.py +31 -41
- {assertpy2-2.5.0 → assertpy2-2.5.1}/tests/test_same_as.py +7 -9
- {assertpy2-2.5.0 → assertpy2-2.5.1}/tests/test_snapshots.py +6 -12
- assertpy2-2.5.1/tests/test_soft.py +188 -0
- assertpy2-2.5.1/tests/test_soft_fail.py +61 -0
- assertpy2-2.5.1/tests/test_string.py +625 -0
- {assertpy2-2.5.0 → assertpy2-2.5.1}/tests/test_type.py +14 -22
- {assertpy2-2.5.0 → assertpy2-2.5.1}/uv.lock +594 -51
- assertpy2-2.5.0/docs/api.md +0 -2120
- assertpy2-2.5.0/tests/test_datetime.py +0 -526
- assertpy2-2.5.0/tests/test_description.py +0 -35
- assertpy2-2.5.0/tests/test_fail.py +0 -17
- assertpy2-2.5.0/tests/test_numbers.py +0 -791
- assertpy2-2.5.0/tests/test_soft.py +0 -219
- assertpy2-2.5.0/tests/test_soft_fail.py +0 -75
- assertpy2-2.5.0/tests/test_string.py +0 -773
- {assertpy2-2.5.0 → assertpy2-2.5.1}/.codecov.yml +0 -0
- {assertpy2-2.5.0 → assertpy2-2.5.1}/.github/dependabot.yml +0 -0
- {assertpy2-2.5.0 → assertpy2-2.5.1}/.github/workflows/ci.yml +0 -0
- {assertpy2-2.5.0 → assertpy2-2.5.1}/.github/workflows/codeql.yml +0 -0
- {assertpy2-2.5.0 → assertpy2-2.5.1}/.github/workflows/publish.yml +0 -0
- {assertpy2-2.5.0 → assertpy2-2.5.1}/.github/workflows/scorecard.yml +0 -0
- {assertpy2-2.5.0 → assertpy2-2.5.1}/.github/workflows/zizmor.yml +0 -0
- {assertpy2-2.5.0 → assertpy2-2.5.1}/CONTRIBUTING.md +0 -0
- {assertpy2-2.5.0 → assertpy2-2.5.1}/LICENSE +0 -0
- {assertpy2-2.5.0 → assertpy2-2.5.1}/SECURITY.md +0 -0
- {assertpy2-2.5.0 → assertpy2-2.5.1}/assertpy2/__init__.py +0 -0
- {assertpy2-2.5.0 → assertpy2-2.5.1}/assertpy2/behave_matchers.py +0 -0
- {assertpy2-2.5.0 → assertpy2-2.5.1}/assertpy2/dynamic.py +0 -0
- {assertpy2-2.5.0 → assertpy2-2.5.1}/assertpy2/errors.py +0 -0
- {assertpy2-2.5.0 → assertpy2-2.5.1}/assertpy2/helpers.py +0 -0
- {assertpy2-2.5.0 → assertpy2-2.5.1}/assertpy2/py.typed +0 -0
- {assertpy2-2.5.0 → assertpy2-2.5.1}/assertpy2/pytest_plugin.py +0 -0
- {assertpy2-2.5.0 → assertpy2-2.5.1}/docs/logo-dark.svg +0 -0
- {assertpy2-2.5.0 → assertpy2-2.5.1}/docs/logo.svg +0 -0
- {assertpy2-2.5.0 → assertpy2-2.5.1}/tests/test_async.py +0 -0
- {assertpy2-2.5.0 → assertpy2-2.5.1}/tests/test_behave_matchers.py +0 -0
- {assertpy2-2.5.0 → assertpy2-2.5.1}/tests/test_bytes.py +0 -0
- {assertpy2-2.5.0 → assertpy2-2.5.1}/tests/test_core.py +0 -0
- {assertpy2-2.5.0 → assertpy2-2.5.1}/tests/test_errors.py +0 -0
- {assertpy2-2.5.0 → assertpy2-2.5.1}/tests/test_grouped_soft.py +0 -0
- {assertpy2-2.5.0 → assertpy2-2.5.1}/tests/test_json.py +0 -0
- {assertpy2-2.5.0 → assertpy2-2.5.1}/tests/test_matcher_registry.py +0 -0
- {assertpy2-2.5.0 → assertpy2-2.5.1}/tests/test_matchers.py +0 -0
- {assertpy2-2.5.0 → assertpy2-2.5.1}/tests/test_matchers_phase3.py +0 -0
- {assertpy2-2.5.0 → assertpy2-2.5.1}/tests/test_not.py +0 -0
- {assertpy2-2.5.0 → assertpy2-2.5.1}/tests/test_overloads.py +0 -0
- {assertpy2-2.5.0 → assertpy2-2.5.1}/tests/test_pipeline.py +0 -0
- {assertpy2-2.5.0 → assertpy2-2.5.1}/tests/test_pytest_plugin.py +0 -0
- {assertpy2-2.5.0 → assertpy2-2.5.1}/tests/test_recursive_compare.py +0 -0
- {assertpy2-2.5.0 → assertpy2-2.5.1}/tests/test_recursive_compare_attrs.py +0 -0
- {assertpy2-2.5.0 → assertpy2-2.5.1}/tests/test_recursive_compare_pydantic.py +0 -0
- {assertpy2-2.5.0 → assertpy2-2.5.1}/tests/test_regex_groups.py +0 -0
- {assertpy2-2.5.0 → assertpy2-2.5.1}/tests/test_rich_diff.py +0 -0
- {assertpy2-2.5.0 → assertpy2-2.5.1}/tests/test_structural.py +0 -0
- {assertpy2-2.5.0 → assertpy2-2.5.1}/tests/test_traceback.py +0 -0
- {assertpy2-2.5.0 → assertpy2-2.5.1}/tests/test_warn.py +0 -0
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
name: Docs
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches: [main]
|
|
6
|
+
pull_request:
|
|
7
|
+
branches: [main]
|
|
8
|
+
|
|
9
|
+
permissions:
|
|
10
|
+
contents: read
|
|
11
|
+
|
|
12
|
+
concurrency:
|
|
13
|
+
group: pages
|
|
14
|
+
cancel-in-progress: false
|
|
15
|
+
|
|
16
|
+
jobs:
|
|
17
|
+
build:
|
|
18
|
+
runs-on: ubuntu-latest
|
|
19
|
+
steps:
|
|
20
|
+
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
|
|
21
|
+
with:
|
|
22
|
+
persist-credentials: false
|
|
23
|
+
|
|
24
|
+
- name: Install uv
|
|
25
|
+
uses: astral-sh/setup-uv@fac544c07dec837d0ccb6301d7b5580bf5edae39 # v8.2.0
|
|
26
|
+
|
|
27
|
+
- name: Set up Python
|
|
28
|
+
uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
|
|
29
|
+
with:
|
|
30
|
+
python-version: "3.14"
|
|
31
|
+
|
|
32
|
+
- name: Install docs dependencies
|
|
33
|
+
run: uv sync --only-group docs
|
|
34
|
+
|
|
35
|
+
- name: Build docs
|
|
36
|
+
run: uv run mkdocs build --strict
|
|
37
|
+
|
|
38
|
+
- name: Upload Pages artifact
|
|
39
|
+
if: github.ref == 'refs/heads/main'
|
|
40
|
+
uses: actions/upload-pages-artifact@fc324d3547104276b827a68afc52ff2a11cc49c9 # v5.0.0
|
|
41
|
+
with:
|
|
42
|
+
path: site
|
|
43
|
+
|
|
44
|
+
deploy:
|
|
45
|
+
if: github.ref == 'refs/heads/main'
|
|
46
|
+
needs: build
|
|
47
|
+
runs-on: ubuntu-latest
|
|
48
|
+
permissions:
|
|
49
|
+
pages: write
|
|
50
|
+
id-token: write
|
|
51
|
+
environment:
|
|
52
|
+
name: github-pages
|
|
53
|
+
url: ${{ steps.deployment.outputs.page_url }}
|
|
54
|
+
steps:
|
|
55
|
+
- name: Deploy to GitHub Pages
|
|
56
|
+
id: deployment
|
|
57
|
+
uses: actions/deploy-pages@cd2ce8fcbc39b97be8ca5fce6e763baed58fa128 # v5.0.0
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: assertpy2
|
|
3
|
-
Version: 2.5.
|
|
3
|
+
Version: 2.5.1
|
|
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
|
|
@@ -26,7 +26,7 @@ Classifier: Programming Language :: Python :: 3.15
|
|
|
26
26
|
Classifier: Topic :: Software Development
|
|
27
27
|
Classifier: Topic :: Software Development :: Testing
|
|
28
28
|
Requires-Python: >=3.10
|
|
29
|
-
Requires-Dist: typing-extensions>=4.0
|
|
29
|
+
Requires-Dist: typing-extensions>=4.0; python_version < '3.11'
|
|
30
30
|
Provides-Extra: allure
|
|
31
31
|
Requires-Dist: allure-pytest>=2.13; extra == 'allure'
|
|
32
32
|
Provides-Extra: behave
|
|
@@ -56,7 +56,7 @@ Description-Content-Type: text/markdown
|
|
|
56
56
|
<a href="https://pypi.org/project/assertpy2/"><img src="https://img.shields.io/pypi/pyversions/assertpy2" alt="Python"></a>
|
|
57
57
|
<a href="https://codecov.io/gh/Solganis/assertpy2"><img src="https://codecov.io/gh/Solganis/assertpy2/graph/badge.svg" alt="Coverage"></a>
|
|
58
58
|
<br>
|
|
59
|
-
<a href="https://github.
|
|
59
|
+
<a href="https://solganis.github.io/assertpy2/"><img src="https://img.shields.io/badge/Docs-online-black" alt="Documentation"></a>
|
|
60
60
|
<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>
|
|
61
61
|
<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>
|
|
62
62
|
<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>
|
|
@@ -123,28 +123,28 @@ Your IDE shows only methods relevant to the value you're testing, not all 100+:
|
|
|
123
123
|
|
|
124
124
|
**Fluent API**
|
|
125
125
|
|
|
126
|
-
- [**Composable matchers**](
|
|
127
|
-
- [**Structural matching**](
|
|
128
|
-
- [**Universal negation**](
|
|
129
|
-
- [**Collection pipeline**](
|
|
130
|
-
- [**Fluent chaining**](
|
|
126
|
+
- [**Composable matchers**](https://solganis.github.io/assertpy2/matchers/): `match.greater_than(5)`, `match.is_uuid()`, combine with `&`, `|`, `~`. Also work with plain `assert ==`.
|
|
127
|
+
- [**Structural matching**](https://solganis.github.io/assertpy2/matchers/#structural-matching): `matches_structure()` for declarative dict/API response validation.
|
|
128
|
+
- [**Universal negation**](https://solganis.github.io/assertpy2/fluent/#universal-negation): `.not_` inverts any assertion without dedicated `is_not_*` methods.
|
|
129
|
+
- [**Collection pipeline**](https://solganis.github.io/assertpy2/fluent/#collection-pipeline): `filtered_on()`, `mapped()`, `flat_mapped()`, `first()`, `last()`, `element()`, `single()`.
|
|
130
|
+
- [**Fluent chaining**](https://solganis.github.io/assertpy2/fluent/#chaining): write assertions as readable one-liners that chain naturally.
|
|
131
131
|
|
|
132
132
|
**Built-in types**
|
|
133
133
|
|
|
134
|
-
- [Strings](
|
|
135
|
-
- [**Bytes assertions**](
|
|
136
|
-
- [**JSON assertions**](
|
|
137
|
-
- [**Dynamic assertions**](
|
|
138
|
-
- [**Dict comparison**](
|
|
139
|
-
- [**Extracting**](
|
|
134
|
+
- [Strings](https://solganis.github.io/assertpy2/assertions/#strings), [numbers](https://solganis.github.io/assertpy2/assertions/#numbers), [lists](https://solganis.github.io/assertpy2/assertions/#lists), [tuples](https://solganis.github.io/assertpy2/assertions/#tuples), [sets](https://solganis.github.io/assertpy2/assertions/#sets), [dicts](https://solganis.github.io/assertpy2/assertions/#dicts), [dates](https://solganis.github.io/assertpy2/assertions/#dates), [booleans](https://solganis.github.io/assertpy2/assertions/#booleans), [objects](https://solganis.github.io/assertpy2/assertions/#objects), [bytes](https://solganis.github.io/assertpy2/assertions/#bytes--bytearray), [files](https://solganis.github.io/assertpy2/assertions/#files), [exceptions](https://solganis.github.io/assertpy2/errors/#expected-exceptions).
|
|
135
|
+
- [**Bytes assertions**](https://solganis.github.io/assertpy2/assertions/#bytes--bytearray): `is_valid_utf8()`, `starts_with_bytes()`, `is_hex_equal_to()`, `decoded_as()` for `bytes`/`bytearray`.
|
|
136
|
+
- [**JSON assertions**](https://solganis.github.io/assertpy2/data/#json-path--schema): JSONPath navigation and JSON Schema validation. `pip install assertpy2[json]`.
|
|
137
|
+
- [**Dynamic assertions**](https://solganis.github.io/assertpy2/assertions/#dynamic-assertions-on-objects): `has_<name>()` for any attribute, property, or zero-argument method.
|
|
138
|
+
- [**Dict comparison**](https://solganis.github.io/assertpy2/assertions/#selective-comparison-ignore--include): `is_equal_to()` with `ignore` and `include` for selective key/field matching (dicts, dataclasses, namedtuples, Pydantic models, attrs, plain objects).
|
|
139
|
+
- [**Extracting**](https://solganis.github.io/assertpy2/assertions/#extracting-attributes-from-objects): flatten collections on attributes with `filter` and `sort` support.
|
|
140
140
|
|
|
141
141
|
**Testing**
|
|
142
142
|
|
|
143
|
-
- [**Soft assertions**](
|
|
144
|
-
- [**Async assertions**](
|
|
145
|
-
- [**Structured errors**](
|
|
146
|
-
- [**Rich pytest diffs**](
|
|
147
|
-
- [**Snapshot testing**](
|
|
143
|
+
- [**Soft assertions**](https://solganis.github.io/assertpy2/testing/#soft-assertions): thread-safe, async-safe via `contextvars`. Group errors with `sa.group()`, or use `assert_all()`.
|
|
144
|
+
- [**Async assertions**](https://solganis.github.io/assertpy2/testing/#async-assertions): `eventually()` with polling/retry for eventual consistency.
|
|
145
|
+
- [**Structured errors**](https://solganis.github.io/assertpy2/errors/#structured-errors): `AssertionFailure` with `.actual`, `.expected`, `.diff` attributes.
|
|
146
|
+
- [**Rich pytest diffs**](https://solganis.github.io/assertpy2/errors/#rich-pytest-diffs): recursive structural diffs for lists, sets, strings, dicts, dataclasses, namedtuples, Pydantic models. Circular reference protection.
|
|
147
|
+
- [**Snapshot testing**](https://solganis.github.io/assertpy2/testing/#snapshot-testing): store and compare data structures in JSON format.
|
|
148
148
|
|
|
149
149
|
**Type safety**
|
|
150
150
|
|
|
@@ -153,16 +153,16 @@ Your IDE shows only methods relevant to the value you're testing, not all 100+:
|
|
|
153
153
|
|
|
154
154
|
**Extensibility**
|
|
155
155
|
|
|
156
|
-
- [**Custom matchers**](
|
|
157
|
-
- [**Regex group extraction**](
|
|
158
|
-
- [**Extensions**](
|
|
156
|
+
- [**Custom matchers**](https://solganis.github.io/assertpy2/matchers/#custom-matchers): `register_matcher()` for domain-specific matchers, composable with `&`, `|`, `~`.
|
|
157
|
+
- [**Regex group extraction**](https://solganis.github.io/assertpy2/data/#regex-group-extraction): `extracting_group()` and `matches_with_groups()` for regex captures.
|
|
158
|
+
- [**Extensions**](https://solganis.github.io/assertpy2/extending/): `add_extension()` for custom assertion methods.
|
|
159
159
|
|
|
160
160
|
**Integrations**
|
|
161
161
|
|
|
162
|
-
- [**Allure**](
|
|
163
|
-
- [**Behave**](
|
|
162
|
+
- [**Allure**](https://solganis.github.io/assertpy2/integrations/#allure): auto-attach structured diff and actual/expected data to reports. `pip install assertpy2[allure]`.
|
|
163
|
+
- [**Behave**](https://solganis.github.io/assertpy2/integrations/#behave): ready-made parameter types for step definitions. `pip install assertpy2[behave]`.
|
|
164
164
|
|
|
165
|
-
See the [full
|
|
165
|
+
See the [full documentation](https://solganis.github.io/assertpy2/) for all assertion methods, examples, and advanced features.
|
|
166
166
|
|
|
167
167
|
---
|
|
168
168
|
|
|
@@ -18,7 +18,7 @@
|
|
|
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.
|
|
21
|
+
<a href="https://solganis.github.io/assertpy2/"><img src="https://img.shields.io/badge/Docs-online-black" alt="Documentation"></a>
|
|
22
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>
|
|
23
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
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>
|
|
@@ -85,28 +85,28 @@ Your IDE shows only methods relevant to the value you're testing, not all 100+:
|
|
|
85
85
|
|
|
86
86
|
**Fluent API**
|
|
87
87
|
|
|
88
|
-
- [**Composable matchers**](
|
|
89
|
-
- [**Structural matching**](
|
|
90
|
-
- [**Universal negation**](
|
|
91
|
-
- [**Collection pipeline**](
|
|
92
|
-
- [**Fluent chaining**](
|
|
88
|
+
- [**Composable matchers**](https://solganis.github.io/assertpy2/matchers/): `match.greater_than(5)`, `match.is_uuid()`, combine with `&`, `|`, `~`. Also work with plain `assert ==`.
|
|
89
|
+
- [**Structural matching**](https://solganis.github.io/assertpy2/matchers/#structural-matching): `matches_structure()` for declarative dict/API response validation.
|
|
90
|
+
- [**Universal negation**](https://solganis.github.io/assertpy2/fluent/#universal-negation): `.not_` inverts any assertion without dedicated `is_not_*` methods.
|
|
91
|
+
- [**Collection pipeline**](https://solganis.github.io/assertpy2/fluent/#collection-pipeline): `filtered_on()`, `mapped()`, `flat_mapped()`, `first()`, `last()`, `element()`, `single()`.
|
|
92
|
+
- [**Fluent chaining**](https://solganis.github.io/assertpy2/fluent/#chaining): write assertions as readable one-liners that chain naturally.
|
|
93
93
|
|
|
94
94
|
**Built-in types**
|
|
95
95
|
|
|
96
|
-
- [Strings](
|
|
97
|
-
- [**Bytes assertions**](
|
|
98
|
-
- [**JSON assertions**](
|
|
99
|
-
- [**Dynamic assertions**](
|
|
100
|
-
- [**Dict comparison**](
|
|
101
|
-
- [**Extracting**](
|
|
96
|
+
- [Strings](https://solganis.github.io/assertpy2/assertions/#strings), [numbers](https://solganis.github.io/assertpy2/assertions/#numbers), [lists](https://solganis.github.io/assertpy2/assertions/#lists), [tuples](https://solganis.github.io/assertpy2/assertions/#tuples), [sets](https://solganis.github.io/assertpy2/assertions/#sets), [dicts](https://solganis.github.io/assertpy2/assertions/#dicts), [dates](https://solganis.github.io/assertpy2/assertions/#dates), [booleans](https://solganis.github.io/assertpy2/assertions/#booleans), [objects](https://solganis.github.io/assertpy2/assertions/#objects), [bytes](https://solganis.github.io/assertpy2/assertions/#bytes--bytearray), [files](https://solganis.github.io/assertpy2/assertions/#files), [exceptions](https://solganis.github.io/assertpy2/errors/#expected-exceptions).
|
|
97
|
+
- [**Bytes assertions**](https://solganis.github.io/assertpy2/assertions/#bytes--bytearray): `is_valid_utf8()`, `starts_with_bytes()`, `is_hex_equal_to()`, `decoded_as()` for `bytes`/`bytearray`.
|
|
98
|
+
- [**JSON assertions**](https://solganis.github.io/assertpy2/data/#json-path--schema): JSONPath navigation and JSON Schema validation. `pip install assertpy2[json]`.
|
|
99
|
+
- [**Dynamic assertions**](https://solganis.github.io/assertpy2/assertions/#dynamic-assertions-on-objects): `has_<name>()` for any attribute, property, or zero-argument method.
|
|
100
|
+
- [**Dict comparison**](https://solganis.github.io/assertpy2/assertions/#selective-comparison-ignore--include): `is_equal_to()` with `ignore` and `include` for selective key/field matching (dicts, dataclasses, namedtuples, Pydantic models, attrs, plain objects).
|
|
101
|
+
- [**Extracting**](https://solganis.github.io/assertpy2/assertions/#extracting-attributes-from-objects): flatten collections on attributes with `filter` and `sort` support.
|
|
102
102
|
|
|
103
103
|
**Testing**
|
|
104
104
|
|
|
105
|
-
- [**Soft assertions**](
|
|
106
|
-
- [**Async assertions**](
|
|
107
|
-
- [**Structured errors**](
|
|
108
|
-
- [**Rich pytest diffs**](
|
|
109
|
-
- [**Snapshot testing**](
|
|
105
|
+
- [**Soft assertions**](https://solganis.github.io/assertpy2/testing/#soft-assertions): thread-safe, async-safe via `contextvars`. Group errors with `sa.group()`, or use `assert_all()`.
|
|
106
|
+
- [**Async assertions**](https://solganis.github.io/assertpy2/testing/#async-assertions): `eventually()` with polling/retry for eventual consistency.
|
|
107
|
+
- [**Structured errors**](https://solganis.github.io/assertpy2/errors/#structured-errors): `AssertionFailure` with `.actual`, `.expected`, `.diff` attributes.
|
|
108
|
+
- [**Rich pytest diffs**](https://solganis.github.io/assertpy2/errors/#rich-pytest-diffs): recursive structural diffs for lists, sets, strings, dicts, dataclasses, namedtuples, Pydantic models. Circular reference protection.
|
|
109
|
+
- [**Snapshot testing**](https://solganis.github.io/assertpy2/testing/#snapshot-testing): store and compare data structures in JSON format.
|
|
110
110
|
|
|
111
111
|
**Type safety**
|
|
112
112
|
|
|
@@ -115,16 +115,16 @@ Your IDE shows only methods relevant to the value you're testing, not all 100+:
|
|
|
115
115
|
|
|
116
116
|
**Extensibility**
|
|
117
117
|
|
|
118
|
-
- [**Custom matchers**](
|
|
119
|
-
- [**Regex group extraction**](
|
|
120
|
-
- [**Extensions**](
|
|
118
|
+
- [**Custom matchers**](https://solganis.github.io/assertpy2/matchers/#custom-matchers): `register_matcher()` for domain-specific matchers, composable with `&`, `|`, `~`.
|
|
119
|
+
- [**Regex group extraction**](https://solganis.github.io/assertpy2/data/#regex-group-extraction): `extracting_group()` and `matches_with_groups()` for regex captures.
|
|
120
|
+
- [**Extensions**](https://solganis.github.io/assertpy2/extending/): `add_extension()` for custom assertion methods.
|
|
121
121
|
|
|
122
122
|
**Integrations**
|
|
123
123
|
|
|
124
|
-
- [**Allure**](
|
|
125
|
-
- [**Behave**](
|
|
124
|
+
- [**Allure**](https://solganis.github.io/assertpy2/integrations/#allure): auto-attach structured diff and actual/expected data to reports. `pip install assertpy2[allure]`.
|
|
125
|
+
- [**Behave**](https://solganis.github.io/assertpy2/integrations/#behave): ready-made parameter types for step definitions. `pip install assertpy2[behave]`.
|
|
126
126
|
|
|
127
|
-
See the [full
|
|
127
|
+
See the [full documentation](https://solganis.github.io/assertpy2/) for all assertion methods, examples, and advanced features.
|
|
128
128
|
|
|
129
129
|
---
|
|
130
130
|
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
"""Internal compatibility shims.
|
|
2
|
+
|
|
3
|
+
``Self`` entered the standard library's :mod:`typing` in Python 3.11; on 3.10 it is provided by
|
|
4
|
+
``typing_extensions``. Re-exporting it from one place lets every mixin import ``Self`` without repeating
|
|
5
|
+
the version gate, and lets ``typing_extensions`` be dropped as a runtime dependency on Python 3.11+.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import sys
|
|
9
|
+
|
|
10
|
+
if sys.version_info >= (3, 11):
|
|
11
|
+
from typing import Self
|
|
12
|
+
else:
|
|
13
|
+
from typing_extensions import Self
|
|
14
|
+
|
|
15
|
+
__all__ = ["Self"]
|
|
@@ -7,8 +7,7 @@ if TYPE_CHECKING:
|
|
|
7
7
|
from pathlib import Path
|
|
8
8
|
from typing import Any, Protocol
|
|
9
9
|
|
|
10
|
-
from
|
|
11
|
-
|
|
10
|
+
from ._compat import Self
|
|
12
11
|
from .assertpy import NegatedBuilder
|
|
13
12
|
from .async_assertions import AsyncAssertionBuilder
|
|
14
13
|
from .matchers import Matcher
|
|
@@ -16,8 +16,7 @@ if TYPE_CHECKING:
|
|
|
16
16
|
import datetime
|
|
17
17
|
import pathlib
|
|
18
18
|
|
|
19
|
-
from
|
|
20
|
-
|
|
19
|
+
from ._compat import Self
|
|
21
20
|
from ._typing import (
|
|
22
21
|
_BytesAssertion,
|
|
23
22
|
_CallableAssertion,
|
|
@@ -47,7 +46,7 @@ from .numeric import NumericMixin
|
|
|
47
46
|
from .snapshot import SnapshotMixin
|
|
48
47
|
from .string import StringMixin
|
|
49
48
|
|
|
50
|
-
__version__ = "2.
|
|
49
|
+
__version__ = "2.5.1"
|
|
51
50
|
|
|
52
51
|
__tracebackhide__ = True # clean tracebacks via py.test integration
|
|
53
52
|
contextlib.__tracebackhide__ = True # ty: ignore[unresolved-attribute] # pytest monkey-patch
|
|
@@ -655,146 +655,214 @@ class _MatchNamespace:
|
|
|
655
655
|
|
|
656
656
|
@staticmethod
|
|
657
657
|
def equal_to(expected: object) -> EqualToMatcher:
|
|
658
|
+
"""Matcher for a value equal to ``expected``."""
|
|
658
659
|
return EqualToMatcher(expected)
|
|
659
660
|
|
|
660
661
|
@staticmethod
|
|
661
662
|
def greater_than(val: object) -> GreaterThanMatcher:
|
|
663
|
+
"""Matcher for a value greater than ``val``."""
|
|
662
664
|
return GreaterThanMatcher(val)
|
|
663
665
|
|
|
664
666
|
@staticmethod
|
|
665
667
|
def greater_than_or_equal_to(val: object) -> GreaterThanOrEqualToMatcher:
|
|
668
|
+
"""Matcher for a value greater than or equal to ``val``."""
|
|
666
669
|
return GreaterThanOrEqualToMatcher(val)
|
|
667
670
|
|
|
668
671
|
@staticmethod
|
|
669
672
|
def less_than(val: object) -> LessThanMatcher:
|
|
673
|
+
"""Matcher for a value less than ``val``."""
|
|
670
674
|
return LessThanMatcher(val)
|
|
671
675
|
|
|
672
676
|
@staticmethod
|
|
673
677
|
def less_than_or_equal_to(val: object) -> LessThanOrEqualToMatcher:
|
|
678
|
+
"""Matcher for a value less than or equal to ``val``."""
|
|
674
679
|
return LessThanOrEqualToMatcher(val)
|
|
675
680
|
|
|
676
681
|
@staticmethod
|
|
677
682
|
def between(low: object, high: object) -> BetweenMatcher:
|
|
683
|
+
"""Matcher for a value in the inclusive range ``low`` to ``high``."""
|
|
678
684
|
return BetweenMatcher(low, high)
|
|
679
685
|
|
|
680
686
|
@staticmethod
|
|
681
687
|
def close_to(expected: object, tolerance: object) -> CloseToMatcher:
|
|
688
|
+
"""Matcher for a value within ``tolerance`` of ``expected`` (``abs(value - expected) <= tolerance``).
|
|
689
|
+
|
|
690
|
+
Args:
|
|
691
|
+
expected: the target value
|
|
692
|
+
tolerance: the maximum allowed absolute difference from ``expected``
|
|
693
|
+
"""
|
|
682
694
|
return CloseToMatcher(expected, tolerance)
|
|
683
695
|
|
|
684
696
|
@staticmethod
|
|
685
697
|
def is_none() -> IsNoneMatcher:
|
|
698
|
+
"""Matcher for ``None``."""
|
|
686
699
|
return IsNoneMatcher()
|
|
687
700
|
|
|
688
701
|
@staticmethod
|
|
689
702
|
def is_not_none() -> IsNotNoneMatcher:
|
|
703
|
+
"""Matcher for any value that is not ``None``."""
|
|
690
704
|
return IsNotNoneMatcher()
|
|
691
705
|
|
|
692
706
|
@staticmethod
|
|
693
707
|
def is_instance_of(expected_type: type) -> IsInstanceOfMatcher:
|
|
708
|
+
"""Matcher for an instance of ``expected_type`` (via ``isinstance``)."""
|
|
694
709
|
return IsInstanceOfMatcher(expected_type)
|
|
695
710
|
|
|
696
711
|
@staticmethod
|
|
697
712
|
def is_truthy() -> IsTruthyMatcher:
|
|
713
|
+
"""Matcher for a truthy value."""
|
|
698
714
|
return IsTruthyMatcher()
|
|
699
715
|
|
|
700
716
|
@staticmethod
|
|
701
717
|
def is_falsy() -> IsFalsyMatcher:
|
|
718
|
+
"""Matcher for a falsy value."""
|
|
702
719
|
return IsFalsyMatcher()
|
|
703
720
|
|
|
704
721
|
@staticmethod
|
|
705
722
|
def has_length(length: int) -> HasLengthMatcher:
|
|
723
|
+
"""Matcher for a value whose ``len()`` equals ``length``."""
|
|
706
724
|
return HasLengthMatcher(length)
|
|
707
725
|
|
|
708
726
|
@staticmethod
|
|
709
727
|
def is_empty() -> IsEmptyMatcher:
|
|
728
|
+
"""Matcher for an empty value (``len() == 0``)."""
|
|
710
729
|
return IsEmptyMatcher()
|
|
711
730
|
|
|
712
731
|
@staticmethod
|
|
713
732
|
def is_not_empty() -> IsNotEmptyMatcher:
|
|
733
|
+
"""Matcher for a non-empty value (``len() > 0``)."""
|
|
714
734
|
return IsNotEmptyMatcher()
|
|
715
735
|
|
|
716
736
|
@staticmethod
|
|
717
737
|
def is_positive() -> IsPositiveMatcher:
|
|
738
|
+
"""Matcher for a value greater than zero."""
|
|
718
739
|
return IsPositiveMatcher()
|
|
719
740
|
|
|
720
741
|
@staticmethod
|
|
721
742
|
def is_negative() -> IsNegativeMatcher:
|
|
743
|
+
"""Matcher for a value less than zero."""
|
|
722
744
|
return IsNegativeMatcher()
|
|
723
745
|
|
|
724
746
|
@staticmethod
|
|
725
747
|
def is_zero() -> IsZeroMatcher:
|
|
748
|
+
"""Matcher for a value equal to zero."""
|
|
726
749
|
return IsZeroMatcher()
|
|
727
750
|
|
|
728
751
|
@staticmethod
|
|
729
752
|
def is_even() -> IsEvenMatcher:
|
|
753
|
+
"""Matcher for an even integer."""
|
|
730
754
|
return IsEvenMatcher()
|
|
731
755
|
|
|
732
756
|
@staticmethod
|
|
733
757
|
def is_odd() -> IsOddMatcher:
|
|
758
|
+
"""Matcher for an odd integer."""
|
|
734
759
|
return IsOddMatcher()
|
|
735
760
|
|
|
736
761
|
@staticmethod
|
|
737
762
|
def is_divisible_by(divisor: int) -> IsDivisibleByMatcher:
|
|
763
|
+
"""Matcher for an integer divisible by ``divisor``."""
|
|
738
764
|
return IsDivisibleByMatcher(divisor)
|
|
739
765
|
|
|
740
766
|
@staticmethod
|
|
741
767
|
def is_callable() -> IsCallableMatcher:
|
|
768
|
+
"""Matcher for a callable object."""
|
|
742
769
|
return IsCallableMatcher()
|
|
743
770
|
|
|
744
771
|
@staticmethod
|
|
745
772
|
def is_in(*values: object) -> IsInMatcher:
|
|
773
|
+
"""Matcher for a value present in ``values``.
|
|
774
|
+
|
|
775
|
+
Args:
|
|
776
|
+
*values: the candidate values; the matched value must equal one of them
|
|
777
|
+
"""
|
|
746
778
|
return IsInMatcher(*values)
|
|
747
779
|
|
|
748
780
|
@staticmethod
|
|
749
781
|
def has_property(name: str, matcher: Matcher | None = None) -> HasPropertyMatcher:
|
|
782
|
+
"""Matcher for an object with attribute ``name``, optionally matching ``matcher``.
|
|
783
|
+
|
|
784
|
+
Args:
|
|
785
|
+
name: the attribute name the object must have
|
|
786
|
+
matcher: optional matcher the attribute value must satisfy; if ``None``,
|
|
787
|
+
only the presence of the attribute is checked
|
|
788
|
+
"""
|
|
750
789
|
return HasPropertyMatcher(name, matcher)
|
|
751
790
|
|
|
752
791
|
@staticmethod
|
|
753
792
|
def contains_string(substring: str) -> ContainsStringMatcher:
|
|
793
|
+
"""Matcher for a string containing ``substring``."""
|
|
754
794
|
return ContainsStringMatcher(substring)
|
|
755
795
|
|
|
756
796
|
@staticmethod
|
|
757
797
|
def matches_regex(pattern: str) -> MatchesRegexMatcher:
|
|
798
|
+
"""Matcher for a string in which ``pattern`` is found (``re.search``)."""
|
|
758
799
|
return MatchesRegexMatcher(pattern)
|
|
759
800
|
|
|
760
801
|
@staticmethod
|
|
761
802
|
def starts_with(prefix: str) -> StartsWithMatcher:
|
|
803
|
+
"""Matcher for a string starting with ``prefix``."""
|
|
762
804
|
return StartsWithMatcher(prefix)
|
|
763
805
|
|
|
764
806
|
@staticmethod
|
|
765
807
|
def ends_with(suffix: str) -> EndsWithMatcher:
|
|
808
|
+
"""Matcher for a string ending with ``suffix``."""
|
|
766
809
|
return EndsWithMatcher(suffix)
|
|
767
810
|
|
|
768
811
|
@staticmethod
|
|
769
812
|
def all_of(*matchers: Matcher) -> AllOfMatcher:
|
|
813
|
+
"""Matcher that holds when every one of ``matchers`` matches (the ``&`` operator)."""
|
|
770
814
|
return AllOfMatcher(*matchers)
|
|
771
815
|
|
|
772
816
|
@staticmethod
|
|
773
817
|
def any_of(*matchers: Matcher) -> AnyOfMatcher:
|
|
818
|
+
"""Matcher that holds when at least one of ``matchers`` matches (the ``|`` operator)."""
|
|
774
819
|
return AnyOfMatcher(*matchers)
|
|
775
820
|
|
|
776
821
|
@staticmethod
|
|
777
822
|
def not_(matcher: Matcher) -> NotMatcher:
|
|
823
|
+
"""Matcher that inverts ``matcher`` (the ``~`` operator)."""
|
|
778
824
|
return NotMatcher(matcher)
|
|
779
825
|
|
|
780
826
|
@staticmethod
|
|
781
827
|
def ignore() -> IgnoreMatcher:
|
|
828
|
+
"""Matcher that accepts anything; useful as a placeholder in ``structure`` specs."""
|
|
782
829
|
return IgnoreMatcher()
|
|
783
830
|
|
|
784
831
|
@staticmethod
|
|
785
832
|
def is_uuid() -> IsUuidMatcher:
|
|
833
|
+
"""Matcher for a string parseable as a UUID."""
|
|
786
834
|
return IsUuidMatcher()
|
|
787
835
|
|
|
788
836
|
@staticmethod
|
|
789
837
|
def is_non_empty_string() -> IsNonEmptyStringMatcher:
|
|
838
|
+
"""Matcher for a non-empty string."""
|
|
790
839
|
return IsNonEmptyStringMatcher()
|
|
791
840
|
|
|
792
841
|
@staticmethod
|
|
793
842
|
def each_item(matcher: Matcher) -> EachMatcher:
|
|
843
|
+
"""Matcher for an iterable whose every item matches ``matcher``.
|
|
844
|
+
|
|
845
|
+
Args:
|
|
846
|
+
matcher: the matcher each item of the iterable must satisfy; a non-iterable
|
|
847
|
+
value never matches
|
|
848
|
+
"""
|
|
794
849
|
return EachMatcher(matcher)
|
|
795
850
|
|
|
796
851
|
@staticmethod
|
|
797
852
|
def structure(spec: dict) -> StructureMatcher:
|
|
853
|
+
"""Matcher for a dict matching ``spec``.
|
|
854
|
+
|
|
855
|
+
Args:
|
|
856
|
+
spec: dict whose values are matchers, raw values (compared with ``==``),
|
|
857
|
+
or nested dict specs. Keys present in the value but absent from the spec are ignored.
|
|
858
|
+
|
|
859
|
+
Examples:
|
|
860
|
+
Usage::
|
|
861
|
+
|
|
862
|
+
assert_that(user).satisfies(
|
|
863
|
+
match.structure({"id": match.is_instance_of(int), "name": "Alice"})
|
|
864
|
+
)
|
|
865
|
+
"""
|
|
798
866
|
return StructureMatcher(spec)
|
|
799
867
|
|
|
800
868
|
def __getattr__(self, name: str) -> Callable[..., BaseMatcher]:
|