The fully-typed fluent assertion library for Python
A modern, batteries-included fork of assertpy
Quick start
pip install assertpy2 # drop-in replacement for assertpy, just change the import
from assertpy2 import assert_that
def test_user():
user = {"name": "Alice", "age": 30, "roles": ["viewer", "editor"]}
assert_that(user).contains_key("name", "age")
assert_that(user).contains_entry({"name": "Alice"})
assert_that(user["age"]).is_between(18, 120)
assert_that(user["roles"]).contains("viewer").does_not_contain("admin")
The full documentation covers every assertion, matcher, and integration.
Why fluent assertions?
A fluent chain reads as one intent and replaces several bare asserts -
and your IDE offers only the methods that fit the value's type:
# bare - three statements, no autocomplete help
assert isinstance(items, list)
assert len(items) == 3
assert "admin" in items
# assertpy2 - one chain, type-aware autocomplete
assert_that(items).is_instance_of(list).is_length(3).contains("admin")
The real difference shows up on failure. Plain assert dumps both structures and leaves you to find the two wrong fields:
assert response == expected
E AssertionError: assert {'id': 1, ...} == {'id': 1, ...}
E Omitting 1 identical items, use -vv to show
E Differing items:
E {'user': {'name': 'Alice', 'role': 'superadmin'}} != {'user': {'name': 'Alice', 'role': 'admin'}}
E {'status': 'active'} != {'status': 'disabled'}
assertpy2 reports the exact path to every difference, in color:
assert_that(response).is_equal_to(expected)
The diff recurses through nested containers, and matcher predicates get the same path-level treatment. For dynamic fields like IDs or timestamps, assert a subset with matches_structure().
Matchers are ordinary values, so they also compose inside the expected structure itself, at any depth, with or without the fluent chain:
response = {"id": 7, "user": {"name": "Alice", "age": 30}, "tags": ["a", "b"]}
assert_that(response).is_equal_to(
{"id": match.greater_than(0), "user": {"name": "Alice", "age": match.between(18, 120)}, "tags": ["a", "b"]}
)
# or keep the bare `assert`, and pytest's own rewriting reports it
assert response == {"id": match.greater_than(0), "user": match.ignore(), "tags": ["a", "b"]}
The fluent form keeps the path-level diff, the bare form keeps pytest's. There are
39 matchers, and they combine with &, | and ~.
Type-aware autocomplete
assert_that() uses @overload to return type-specific Protocols.
Your IDE shows only methods relevant to the value you're testing, not all 100+:
assert_that("hello").→ string methods:starts_with,matches,is_alpha, ...assert_that(42).→ numeric methods:is_positive,is_between,is_close_to, ...assert_that(Path("/tmp")).→ path methods:exists,is_file,is_readable, ...assert_that(my_dict).→ dict methods:contains_key,contains_entry,has_json_path, ...assert_that(b"\x89PNG").→ bytes methods:starts_with_bytes,is_valid_utf8,decoded_as, ...
9 type-specific Protocols instead of one Any.
Works in PyCharm, VS Code, and any LSP-compatible editor.
Typed narrowing
An assertion hands the value back, statically narrowed. is_not_none() strips None,
is_instance_of() narrows to the class, and .value returns it with no cast and no bare assert:
order = assert_that(repo.find(42)).is_not_none().is_instance_of(PaidOrder).value
order.refund() # statically PaidOrder - verified by ty, mypy, and pyright
For API tests, assert_conforms() validates a raw payload against a Pydantic model and narrows the chain to it,
with exact=True catching silent contract drift:
data = assert_conforms(response.json(), OrderModel).value # data: OrderModel
Features
Fluent API
- Composable matchers:
match.greater_than(5),match.is_uuid(), combine with&,|,~, usable in plainassert ==. - Structural matching:
matches_structure()for declarative dict/API-response validation. - Recursive field assertions:
all_fields_satisfy()/has_no_none_fields()apply a predicate to every leaf of an object graph. - Vacuous-assertion guard:
--assertpy2-vacuouswarns when a universal assertion passes over an empty collection, having checked nothing. - Universal negation:
.not_inverts any assertion, no dedicatedis_not_*methods. - Collection pipeline:
filtered_on(),mapped(),flat_mapped(),first(),last(),element(),single(). - Positional & pairwise checks:
satisfies_exactly(),zip_satisfies(),contains_only_once(),has_same_size_as(), plus*_in_any_ordervariants. - Fluent chaining: chain assertions into readable one-liners.
Type safety
- Type-aware autocomplete: 9 Protocols, IDE shows only relevant methods per type.
- Typed narrowing:
.valuehands the checked value back, statically narrowed byis_not_none(),is_instance_of(), and asatisfies()TypeIspredicate. - Contract testing:
assert_conforms()validates a raw payload against a Pydantic model and narrows to it.exact=Truecatches contract drift,each=Truevalidates list endpoints.
Built-in types
- Strings, numbers, lists, tuples, sets, dicts, dates, booleans, objects, bytes, files, exceptions.
- Bytes assertions:
is_valid_utf8(),starts_with_bytes(),is_hex_equal_to(),decoded_as()forbytes/bytearray. - Dynamic assertions:
has_<name>()for any attribute, property, or zero-argument method. - Dict comparison:
is_equal_to(ignore=..., include=...)for selective key/field matching by name, regex, or type. - Recursive comparison:
is_equal_to()withtolerance,comparators, orignore_nullfor nested structures. - Extracting: flatten collections on attributes with
filterandsortsupport.
Testing
- Soft assertions: thread-safe and async-safe via
contextvars, each failure reported with itsfile:line. Group withsa.group()orassert_all(). - Polling assertions:
eventually()(async) /eventually_sync()(blocking) retry for eventual consistency, with a convergence trace on timeout. - Expected exceptions:
raises().when_called_with(), walk the cause chain (caused_by(),has_root_cause()), matchExceptionGroup(contains_error()), or pivot to the object (raised()). - Structured errors:
AssertionFailurecarries.actual,.expected,.diff, and the diff renders into the message itself, so it shows off pytest too. - Assertions as values:
check()runs the next assertion for its verdict instead of raising, returning anAssertionOutcomethat is truthy when it held and carries the message, values and diff when it did not. - Rich pytest diffs: recursive structural diffs across containers, dataclasses, attrs, and Pydantic models, with intra-line carets for strings and circular-reference protection.
- Snapshot testing: three modes under one typed API, all updated with
--assertpy2-snapshot-update:snapshot()(external JSON file),matches_inline()(recorded into the test source), andmatches_contract_snapshot()(value-tolerant structural regressions). - OpenAPI response contracts:
conforms_to_openapi(spec, path, method)validates a JSON response body against an operation's response schema (OpenAPI 3.0/3.1 and Swagger 2.0), reporting every violation with its JSON path.
Extensibility
- Custom matchers:
register_matcher()to compose existing ones, or subclassBaseMatcherfor a rule that needs its own predicate. Both compose with&,|,~. - Regex group extraction:
extracting_group()andmatches_with_groups()for regex captures. - Extensions:
add_extension()for custom assertion methods.
Integrations
- Allure (
pip install assertpy2[allure]): the pytest plugin auto-attaches structured diff and actual/expected data to Allure reports, in three configurable modes. - Behave (
pip install assertpy2[behave]): ready-made parameter types (PositiveInt,NonEmptyString, ...) for step definitions like{age:PositiveInt}. - JSON (
pip install assertpy2[json]): JSONPath navigation (at_json_path(),has_json_path()) and JSON Schema validation (matches_json_schema()). - Data frames (
pip install assertpy2[pandas]/[polars]/[numpy]): fluent equality for pandas/polars frames and numpy arrays, carrying each library's own diff.
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file assertpy2-2.20.1.tar.gz.
File metadata
- Download URL: assertpy2-2.20.1.tar.gz
- Upload date:
- Size: 686.4 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via:
twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
319ed906c27f0b5b5b338b59c7efab3a5e9a16a36870084f8cb052083e8039fe
|
|
| MD5 |
a549e841ad891ca8ef6b33ad9430cb72
|
|
| BLAKE2b-256 |
1d6d460f6c89fec447159e37be50729d7df8d72afab32b8cda19c67516788dad
|
Provenance
The following attestation bundles were made for assertpy2-2.20.1.tar.gz:
Publisher:
publish.yml on Solganis/assertpy2
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
assertpy2-2.20.1.tar.gz -
Subject digest:
319ed906c27f0b5b5b338b59c7efab3a5e9a16a36870084f8cb052083e8039fe - Sigstore transparency entry: 2414031272
- Sigstore integration time:
-
Permalink:
Solganis/assertpy2@6595f1e9dc985bc652d1d8319f47ebd8617b5ba4 -
Branch / Tag:
refs/tags/v2.20.1 - Owner: https://github.com/Solganis
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@6595f1e9dc985bc652d1d8319f47ebd8617b5ba4 -
Trigger Event:
release
-
Statement type:
File details
Details for the file assertpy2-2.20.1-py3-none-any.whl.
File metadata
- Download URL: assertpy2-2.20.1-py3-none-any.whl
- Upload date:
- Size: 179.3 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via:
twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
37ca1950ec76bc2848659829a8812880fa0636170db2f302550a5d35d8ffc5df
|
|
| MD5 |
052c5a91a3575aa3cc35ebc6f70cd42a
|
|
| BLAKE2b-256 |
efb08de58020cbe0d2103ec7923e0e4d16e26243dca4f742851851ee4da63ee8
|
Provenance
The following attestation bundles were made for assertpy2-2.20.1-py3-none-any.whl:
Publisher:
publish.yml on Solganis/assertpy2
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
assertpy2-2.20.1-py3-none-any.whl -
Subject digest:
37ca1950ec76bc2848659829a8812880fa0636170db2f302550a5d35d8ffc5df - Sigstore transparency entry: 2414031315
- Sigstore integration time:
-
Permalink:
Solganis/assertpy2@6595f1e9dc985bc652d1d8319f47ebd8617b5ba4 -
Branch / Tag:
refs/tags/v2.20.1 - Owner: https://github.com/Solganis
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@6595f1e9dc985bc652d1d8319f47ebd8617b5ba4 -
Trigger Event:
release
-
Statement type: