Skip to main content

Fluent assertion library for Python with composable matchers, structural matching, and full type safety

Project description

assertpy2

Fluent assertion library for Python with composable matchers, structural matching, and full type safety.
A modern, batteries-included fork of assertpy.

CI PyPI version Downloads Python Coverage
Documentation Ruff uv ty OpenSSF Scorecard OpenSSF Best Practices
public overloads type-checked by ty, mypy --strict, and pyright with zero suppressions


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["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.

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_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)

Structured diff in the terminal: user.role shown with its path, removal in red and addition in green

Recursive diffs work for dicts, dataclasses, namedtuples, 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:

Structured diffs in the terminal: dict path, list element, set extra/missing, and structural-matcher predicate diffs, side by side

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.

See the Type Safety guide for the full walkthrough.

Features

Fluent API

  • Composable matchers: match.greater_than(5), match.is_uuid(), combine with &, |, ~. Also work with plain assert ==.
  • Structural matching: matches_structure() for declarative dict/API response validation, reporting the exact path to each mismatch on failure.
  • Recursive field assertions: all_fields_satisfy() / has_no_none_fields() apply a predicate to every leaf of an object graph, reporting the exact path.
  • Universal negation: .not_ inverts any assertion without dedicated is_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_order variants.
  • 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() for bytes/bytearray.
  • Dynamic assertions: has_<name>() for any attribute, property, or zero-argument method.
  • Dict comparison: is_equal_to() with ignore and include for selective key/field matching (dicts, dataclasses, namedtuples, Pydantic models, attrs, plain objects), including by regex or type.
  • Recursive comparison: is_equal_to() with tolerance (absolute float tolerance at any depth), comparators (per-type / per-field predicates), or ignore_null (skip fields the expected template leaves None) for nested structures.
  • Extracting: flatten collections on attributes with filter and sort support.

Testing

  • Soft assertions: thread-safe, async-safe via contextvars; each collected failure is reported with its file:line. Group errors with sa.group(), or use assert_all().
  • Polling assertions: eventually() (async) / eventually_sync() (blocking) retry for eventual consistency, with a convergence trace pinpointing why a timeout never settled.
  • Expected exceptions: raises().when_called_with() then assert on the message, walk the cause chain (caused_by(), has_root_cause()), match an ExceptionGroup (contains_error()), or pivot to the exception object (raised()).
  • Structured errors: AssertionFailure with .actual, .expected, .diff attributes.
  • 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; update via --assertpy2-snapshot-update. matches_contract_snapshot() catches structural regressions, value-tolerant.

Type safety

  • Type-aware autocomplete: 9 Protocols, IDE shows only relevant methods per type.
  • Typed narrowing: .value hands the checked value back; is_not_none(), is_instance_of(), and a satisfies() TypeIs predicate narrow its static type - no casts.
  • Contract testing: assert_conforms() validates a raw payload against a Pydantic v2 model and narrows the chain to it - the capstone for API-response tests; exact=True catches silent contract drift (undeclared fields), each=True validates list endpoints.
  • py.typed: Self return types, PEP 561 compliant (PEP 561).

Extensibility

  • Custom matchers: register_matcher() for domain-specific matchers, composable with &, |, ~.
  • Regex group extraction: extracting_group() and matches_with_groups() for regex captures.
  • Extensions: add_extension() for custom assertion methods.

See the full documentation for all assertion methods, examples, and advanced features.

Integrations

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.

BSD 3-Clause License

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

assertpy2-2.15.0.tar.gz (536.3 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

assertpy2-2.15.0-py3-none-any.whl (113.6 kB view details)

Uploaded Python 3

File details

Details for the file assertpy2-2.15.0.tar.gz.

File metadata

  • Download URL: assertpy2-2.15.0.tar.gz
  • Upload date:
  • Size: 536.3 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.13

File hashes

Hashes for assertpy2-2.15.0.tar.gz
Algorithm Hash digest
SHA256 b8057d8ec8fef4601b63403135e3c9e0f91472b64cc620fb52627e4201bd05a6
MD5 18675e8abeefaf6ee0c975dff11cff7f
BLAKE2b-256 bf2ecf1de744b0e66bd52de486beb6454264bced9cf8c60b6733cbdbc5bafd5e

See more details on using hashes here.

Provenance

The following attestation bundles were made for assertpy2-2.15.0.tar.gz:

Publisher: publish.yml on Solganis/assertpy2

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file assertpy2-2.15.0-py3-none-any.whl.

File metadata

  • Download URL: assertpy2-2.15.0-py3-none-any.whl
  • Upload date:
  • Size: 113.6 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.13

File hashes

Hashes for assertpy2-2.15.0-py3-none-any.whl
Algorithm Hash digest
SHA256 3e6093111847281c3f1e05699a04464620e88bd0e311cbc94973d1b3a088ee4d
MD5 6c70e7fa7115d27f0f229f04c2adb316
BLAKE2b-256 7ad63d3c4a4d488b4ccce8e4883cc7fe21dfdbf2229dfc1dd97f86861110a715

See more details on using hashes here.

Provenance

The following attestation bundles were made for assertpy2-2.15.0-py3-none-any.whl:

Publisher: publish.yml on Solganis/assertpy2

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page