Fluent assertion library for Python with composable matchers, structural matching, and full type safety.
A modern, batteries-included fork of assertpy.
pip install assertpy2 # drop-in replacement for assertpy, just change the importfrom 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["age"]).is_between(18, 120)
assert_that(user["roles"]).contains("viewer").does_not_contain("admin")
assert_that(user).has_name("Alice")Browse the full documentation for every assertion, matcher, and integration.
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_type_of(list).is_length(3).contains("admin")The real difference shows up when a test fails. Here a nested response has two wrong
fields. Plain assert dumps both structures and leaves you to find them:
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)Recursive diffs work for dicts, dataclasses, namedtuples, attrs, and Pydantic models.
For responses with dynamic fields (IDs, timestamps), validate a subset with
matches_structure() instead of exact equality.
The same path-level treatment for dicts, lists, sets, and matcher predicates:
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.
See the Type Safety guide for the full walkthrough.
Fluent API
- Composable matchers:
match.greater_than(5),match.is_uuid(), combine with&,|,~. Also work with plainassert ==. - Structural matching:
matches_structure()for declarative dict/API response validation, reporting the exact path to each mismatch on failure. - Universal negation:
.not_inverts any assertion without dedicatedis_not_*methods. - Collection pipeline:
filtered_on(),mapped(),flat_mapped(),first(),last(),element(),single(). - Fluent chaining: write assertions as readable one-liners that chain naturally.
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()withignoreandincludefor selective key/field matching (dicts, dataclasses, namedtuples, Pydantic models, attrs, plain objects). - Extracting: flatten collections on attributes with
filterandsortsupport.
Testing
- Soft assertions: thread-safe, async-safe via
contextvars. Group errors withsa.group(), or useassert_all(). - Async assertions:
eventually()with polling/retry for eventual consistency. - Structured errors:
AssertionFailurewith.actual,.expected,.diffattributes. - Rich pytest diffs: recursive structural diffs for lists, sets, strings, dicts, dataclasses, namedtuples, Pydantic models, and matcher-based assertions (
matches_structure(),satisfies(),each()). Circular reference protection. - Snapshot testing: store and compare data structures in JSON format.
- Property-based tested: comparison, selective-diff, matcher algebra, and collection logic are checked with Hypothesis against reference semantics, on top of 100% branch coverage.
Type safety
- Type-aware autocomplete: 9 Protocols, IDE shows only relevant methods per type.
- py.typed:
Selfreturn types, PEP 561 compliant (PEP 561).
Extensibility
- Custom matchers:
register_matcher()for domain-specific matchers, composable with&,|,~. - Regex group extraction:
extracting_group()andmatches_with_groups()for regex captures. - Extensions:
add_extension()for custom assertion methods.
See the full documentation for all assertion methods, examples, and advanced features.
Optional adapters, each its own extra; full configuration and examples are in the Integrations guide.
- 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.

