Skip to content

Matchers

The composable matchers exposed by the match namespace.

Call them as match.greater_than(5), match.is_uuid(), and so on, then combine with &, |, and ~. See the Matchers guide for usage, composition, and custom matchers.

Factory namespace for creating matcher instances.

Usage:

from assertpy2 import match

assert_that(value).satisfies(match.greater_than(5) & match.less_than(10))
assert_that(items).each(match.is_positive())

equal_to staticmethod

equal_to(
    expected: object,
    strict_types: bool = False,
    **options: object,
) -> EqualToMatcher

Matcher for a value equal to expected.

Parameters:

Name Type Description Default
expected object

the value to compare against

required
strict_types bool

also require the same type, so True no longer matches 1. The same relation is_equal_to(..., strict_types=True) applies, spelled for a spec.

False
**options object

the rest of what is_equal_to() accepts, with the same meaning: tolerance, comparators, ignore, include and ignore_null. One relation, one set of knobs, whichever way it is spelled.

{}

Examples:

Usage:

assert_that(reading).satisfies(match.equal_to(expected, tolerance=0.01))
assert_that(payload).matches_structure({"user": match.equal_to(user, ignore="updated_at")})
Source code in assertpy2/matchers.py
@staticmethod
def equal_to(expected: object, strict_types: bool = False, **options: object) -> EqualToMatcher:
    """Matcher for a value equal to ``expected``.

    Args:
        expected: the value to compare against
        strict_types: also require the same type, so ``True`` no longer matches ``1``.  The same
            relation ``is_equal_to(..., strict_types=True)`` applies, spelled for a spec.
        **options: the rest of what
            [`is_equal_to()`][assertpy2.base.BaseMixin.is_equal_to] accepts, with the same meaning:
            ``tolerance``, ``comparators``, ``ignore``, ``include`` and ``ignore_null``.  One
            relation, one set of knobs, whichever way it is spelled.

    Examples:
        Usage:

            assert_that(reading).satisfies(match.equal_to(expected, tolerance=0.01))
            assert_that(payload).matches_structure({"user": match.equal_to(user, ignore="updated_at")})
    """
    return EqualToMatcher(expected, strict_types, **options)

greater_than staticmethod

greater_than(val: object) -> GreaterThanMatcher

Matcher for a value greater than val.

Source code in assertpy2/matchers.py
@staticmethod
def greater_than(val: object) -> GreaterThanMatcher:
    """Matcher for a value greater than ``val``."""
    return GreaterThanMatcher(val)

greater_than_or_equal_to staticmethod

greater_than_or_equal_to(
    val: object,
) -> GreaterThanOrEqualToMatcher

Matcher for a value greater than or equal to val.

Source code in assertpy2/matchers.py
@staticmethod
def greater_than_or_equal_to(val: object) -> GreaterThanOrEqualToMatcher:
    """Matcher for a value greater than or equal to ``val``."""
    return GreaterThanOrEqualToMatcher(val)

less_than staticmethod

less_than(val: object) -> LessThanMatcher

Matcher for a value less than val.

Source code in assertpy2/matchers.py
@staticmethod
def less_than(val: object) -> LessThanMatcher:
    """Matcher for a value less than ``val``."""
    return LessThanMatcher(val)

less_than_or_equal_to staticmethod

less_than_or_equal_to(
    val: object,
) -> LessThanOrEqualToMatcher

Matcher for a value less than or equal to val.

Source code in assertpy2/matchers.py
@staticmethod
def less_than_or_equal_to(val: object) -> LessThanOrEqualToMatcher:
    """Matcher for a value less than or equal to ``val``."""
    return LessThanOrEqualToMatcher(val)

between staticmethod

between(low: object, high: object) -> BetweenMatcher

Matcher for a value in the inclusive range low to high.

Source code in assertpy2/matchers.py
@staticmethod
def between(low: object, high: object) -> BetweenMatcher:
    """Matcher for a value in the inclusive range ``low`` to ``high``."""
    return BetweenMatcher(low, high)

close_to staticmethod

close_to(
    expected: object, tolerance: object
) -> CloseToMatcher

Matcher for a value within tolerance of expected (abs(value - expected) <= tolerance).

An infinity is close only to itself, whatever the tolerance, and a NaN to nothing.

Parameters:

Name Type Description Default
expected object

the target value

required
tolerance object

the maximum allowed absolute difference from expected

required
Source code in assertpy2/matchers.py
@staticmethod
def close_to(expected: object, tolerance: object) -> CloseToMatcher:
    """Matcher for a value within ``tolerance`` of ``expected`` (``abs(value - expected) <= tolerance``).

    An infinity is close only to itself, whatever the tolerance, and a NaN to nothing.

    Args:
        expected: the target value
        tolerance: the maximum allowed absolute difference from ``expected``
    """
    return CloseToMatcher(expected, tolerance)

is_none staticmethod

is_none() -> IsNoneMatcher

Matcher for None.

Source code in assertpy2/matchers.py
@staticmethod
def is_none() -> IsNoneMatcher:
    """Matcher for ``None``."""
    return IsNoneMatcher()

is_not_none staticmethod

is_not_none() -> IsNotNoneMatcher

Matcher for any value that is not None.

Source code in assertpy2/matchers.py
@staticmethod
def is_not_none() -> IsNotNoneMatcher:
    """Matcher for any value that is not ``None``."""
    return IsNotNoneMatcher()

is_instance_of staticmethod

is_instance_of(
    expected_type: ClassInfo,
) -> IsInstanceOfMatcher

Matcher for an instance of expected_type (via isinstance).

Accepts whatever isinstance accepts: a class, a union (int | str), or a tuple of either. The builder assertion of the same name stays narrower on purpose - its overloads refine the tracked value to the given class, and a union has no single class to refine to. Reach for is_instance_of_any there.

Source code in assertpy2/matchers.py
@staticmethod
def is_instance_of(expected_type: ClassInfo) -> IsInstanceOfMatcher:
    """Matcher for an instance of ``expected_type`` (via ``isinstance``).

    Accepts whatever ``isinstance`` accepts: a class, a union (``int | str``), or a tuple of
    either.  The builder assertion of the same name stays narrower on purpose - its overloads
    refine the tracked value to the given class, and a union has no single class to refine to.
    Reach for `is_instance_of_any` there.
    """
    return IsInstanceOfMatcher(expected_type)

is_type_of staticmethod

is_type_of(expected_type: type) -> IsTypeOfMatcher

Matcher for exactly expected_type, rejecting subclasses (int but not bool).

Source code in assertpy2/matchers.py
@staticmethod
def is_type_of(expected_type: type) -> IsTypeOfMatcher:
    """Matcher for exactly ``expected_type``, rejecting subclasses (``int`` but not ``bool``)."""
    return IsTypeOfMatcher(expected_type)

is_truthy staticmethod

is_truthy() -> IsTruthyMatcher

Matcher for a truthy value.

Source code in assertpy2/matchers.py
@staticmethod
def is_truthy() -> IsTruthyMatcher:
    """Matcher for a truthy value."""
    return IsTruthyMatcher()

is_falsy staticmethod

is_falsy() -> IsFalsyMatcher

Matcher for a falsy value.

Source code in assertpy2/matchers.py
@staticmethod
def is_falsy() -> IsFalsyMatcher:
    """Matcher for a falsy value."""
    return IsFalsyMatcher()

has_length staticmethod

has_length(length: SupportsIndex) -> HasLengthMatcher

Matcher for a value whose len() equals length.

Source code in assertpy2/matchers.py
@staticmethod
def has_length(length: SupportsIndex) -> HasLengthMatcher:
    """Matcher for a value whose ``len()`` equals ``length``."""
    return HasLengthMatcher(length)

is_length staticmethod

is_length(length: SupportsIndex) -> HasLengthMatcher

Matcher for a value whose len() equals length.

The same matcher as has_length(), under the name the fluent assertion uses (is_length()). One relation was reachable as has_length from the matcher namespace and as is_length from the builder, so which name worked depended on which of the two a reader had seen first. Both work from both now.

Source code in assertpy2/matchers.py
@staticmethod
def is_length(length: SupportsIndex) -> HasLengthMatcher:
    """Matcher for a value whose ``len()`` equals ``length``.

    The same matcher as `has_length()`, under the name the fluent assertion uses
    ([`is_length()`][assertpy2.base.BaseMixin.is_length]).  One relation was reachable as
    ``has_length`` from the matcher namespace and as ``is_length`` from the builder, so which name
    worked depended on which of the two a reader had seen first.  Both work from both now.
    """
    return HasLengthMatcher(length)

is_empty staticmethod

is_empty() -> IsEmptyMatcher

Matcher for an empty value (len() == 0).

Source code in assertpy2/matchers.py
@staticmethod
def is_empty() -> IsEmptyMatcher:
    """Matcher for an empty value (``len() == 0``)."""
    return IsEmptyMatcher()

is_not_empty staticmethod

is_not_empty() -> IsNotEmptyMatcher

Matcher for a non-empty value (len() > 0).

Source code in assertpy2/matchers.py
@staticmethod
def is_not_empty() -> IsNotEmptyMatcher:
    """Matcher for a non-empty value (``len() > 0``)."""
    return IsNotEmptyMatcher()

is_positive staticmethod

is_positive() -> IsPositiveMatcher

Matcher for a value greater than zero.

Source code in assertpy2/matchers.py
@staticmethod
def is_positive() -> IsPositiveMatcher:
    """Matcher for a value greater than zero."""
    return IsPositiveMatcher()

is_negative staticmethod

is_negative() -> IsNegativeMatcher

Matcher for a value less than zero.

Source code in assertpy2/matchers.py
@staticmethod
def is_negative() -> IsNegativeMatcher:
    """Matcher for a value less than zero."""
    return IsNegativeMatcher()

is_zero staticmethod

is_zero() -> IsZeroMatcher

Matcher for a value equal to zero.

Source code in assertpy2/matchers.py
@staticmethod
def is_zero() -> IsZeroMatcher:
    """Matcher for a value equal to zero."""
    return IsZeroMatcher()

is_even staticmethod

is_even() -> IsEvenMatcher

Matcher for an even integer.

Source code in assertpy2/matchers.py
@staticmethod
def is_even() -> IsEvenMatcher:
    """Matcher for an even integer."""
    return IsEvenMatcher()

is_odd staticmethod

is_odd() -> IsOddMatcher

Matcher for an odd integer.

Source code in assertpy2/matchers.py
@staticmethod
def is_odd() -> IsOddMatcher:
    """Matcher for an odd integer."""
    return IsOddMatcher()

is_divisible_by staticmethod

is_divisible_by(
    divisor: SupportsIndex,
) -> IsDivisibleByMatcher

Matcher for an integer divisible by divisor.

Source code in assertpy2/matchers.py
@staticmethod
def is_divisible_by(divisor: SupportsIndex) -> IsDivisibleByMatcher:
    """Matcher for an integer divisible by ``divisor``."""
    return IsDivisibleByMatcher(divisor)

is_callable staticmethod

is_callable() -> IsCallableMatcher

Matcher for a callable object.

Source code in assertpy2/matchers.py
@staticmethod
def is_callable() -> IsCallableMatcher:
    """Matcher for a callable object."""
    return IsCallableMatcher()

is_in staticmethod

is_in(*values: object) -> IsInMatcher

Matcher for a value present in values.

Parameters:

Name Type Description Default
*values object

the candidate values; the matched value must equal one of them

()
Source code in assertpy2/matchers.py
@staticmethod
def is_in(*values: object) -> IsInMatcher:
    """Matcher for a value present in ``values``.

    Args:
        *values: the candidate values; the matched value must equal one of them
    """
    return IsInMatcher(*values)

has_property staticmethod

has_property(
    name: str, matcher: Matcher[Any] | None = None
) -> HasPropertyMatcher

Matcher for an object with attribute name, optionally matching matcher.

Parameters:

Name Type Description Default
name str

the attribute name the object must have

required
matcher Matcher[Any] | None

optional matcher the attribute value must satisfy; if None, only the presence of the attribute is checked

None
Source code in assertpy2/matchers.py
@staticmethod
def has_property(name: str, matcher: Matcher[Any] | None = None) -> HasPropertyMatcher:
    """Matcher for an object with attribute ``name``, optionally matching ``matcher``.

    Args:
        name: the attribute name the object must have
        matcher: optional matcher the attribute value must satisfy; if ``None``,
            only the presence of the attribute is checked
    """
    return HasPropertyMatcher(name, matcher)

contains_string staticmethod

contains_string(
    substring: str | bytes,
) -> Matcher[str | bytes]

Matcher for text containing substring, on str and on bytes alike.

Source code in assertpy2/matchers.py
@staticmethod
def contains_string(substring: str | bytes) -> Matcher[str | bytes]:
    """Matcher for text containing ``substring``, on `str` and on `bytes` alike."""
    return ContainsStringMatcher(substring)

matches_regex staticmethod

matches_regex(pattern: str) -> Matcher[str]

Matcher for a string in which pattern is found (re.search).

Source code in assertpy2/matchers.py
@staticmethod
def matches_regex(pattern: str) -> Matcher[str]:
    """Matcher for a string in which ``pattern`` is found (``re.search``)."""
    return MatchesRegexMatcher(pattern)

starts_with staticmethod

starts_with(prefix: str | bytes) -> Matcher[str | bytes]

Matcher for text starting with prefix, on str and on bytes alike.

Source code in assertpy2/matchers.py
@staticmethod
def starts_with(prefix: str | bytes) -> Matcher[str | bytes]:
    """Matcher for text starting with ``prefix``, on `str` and on `bytes` alike."""
    return StartsWithMatcher(prefix)

ends_with staticmethod

ends_with(suffix: str | bytes) -> Matcher[str | bytes]

Matcher for text ending with suffix, on str and on bytes alike.

Source code in assertpy2/matchers.py
@staticmethod
def ends_with(suffix: str | bytes) -> Matcher[str | bytes]:
    """Matcher for text ending with ``suffix``, on `str` and on `bytes` alike."""
    return EndsWithMatcher(suffix)

all_of staticmethod

all_of(*matchers: Matcher[Any]) -> AllOfMatcher

Matcher that holds when every one of matchers matches (the & operator).

Source code in assertpy2/matchers.py
@staticmethod
def all_of(*matchers: Matcher[Any]) -> AllOfMatcher:
    """Matcher that holds when every one of ``matchers`` matches (the ``&`` operator)."""
    return AllOfMatcher(*matchers)

any_of staticmethod

any_of(*matchers: Matcher[Any]) -> AnyOfMatcher

Matcher that holds when at least one of matchers matches (the | operator).

Source code in assertpy2/matchers.py
@staticmethod
def any_of(*matchers: Matcher[Any]) -> AnyOfMatcher:
    """Matcher that holds when at least one of ``matchers`` matches (the ``|`` operator)."""
    return AnyOfMatcher(*matchers)

not_ staticmethod

not_(matcher: Matcher[Any]) -> NotMatcher

Matcher that inverts matcher (the ~ operator).

Source code in assertpy2/matchers.py
@staticmethod
def not_(matcher: Matcher[Any]) -> NotMatcher:
    """Matcher that inverts ``matcher`` (the ``~`` operator)."""
    return NotMatcher(matcher)

ignore staticmethod

ignore() -> IgnoreMatcher

Matcher that accepts anything; useful as a placeholder in structure specs.

Source code in assertpy2/matchers.py
@staticmethod
def ignore() -> IgnoreMatcher:
    """Matcher that accepts anything; useful as a placeholder in ``structure`` specs."""
    return IgnoreMatcher()

is_uuid staticmethod

is_uuid() -> IsUuidMatcher

Matcher for a string parseable as a UUID.

Source code in assertpy2/matchers.py
@staticmethod
def is_uuid() -> IsUuidMatcher:
    """Matcher for a string parseable as a UUID."""
    return IsUuidMatcher()

is_non_empty_string staticmethod

is_non_empty_string() -> IsNonEmptyStringMatcher

Matcher for a non-empty string.

Source code in assertpy2/matchers.py
@staticmethod
def is_non_empty_string() -> IsNonEmptyStringMatcher:
    """Matcher for a non-empty string."""
    return IsNonEmptyStringMatcher()

is_now staticmethod

is_now(delta: float | timedelta = 2.0) -> IsNowMatcher

Matcher for a datetime within delta of the current time.

Parameters:

Name Type Description Default
delta float | timedelta

tolerance as seconds (a number) or a timedelta; defaults to 2 seconds. Naive and timezone-aware values are both handled (compared against now in the same awareness).

2.0
Source code in assertpy2/matchers.py
@staticmethod
def is_now(delta: float | timedelta = 2.0) -> IsNowMatcher:
    """Matcher for a ``datetime`` within ``delta`` of the current time.

    Args:
        delta: tolerance as seconds (a number) or a ``timedelta``; defaults to 2 seconds. Naive and
            timezone-aware values are both handled (compared against ``now`` in the same awareness).
    """
    return IsNowMatcher(delta if isinstance(delta, timedelta) else timedelta(seconds=delta))

is_before staticmethod

is_before(other: datetime) -> IsBeforeMatcher

Matcher for a datetime strictly before other (a non-comparable value never matches).

Source code in assertpy2/matchers.py
@staticmethod
def is_before(other: datetime) -> IsBeforeMatcher:
    """Matcher for a ``datetime`` strictly before ``other`` (a non-comparable value never matches)."""
    return IsBeforeMatcher(other)

is_after staticmethod

is_after(other: datetime) -> IsAfterMatcher

Matcher for a datetime strictly after other (a non-comparable value never matches).

Source code in assertpy2/matchers.py
@staticmethod
def is_after(other: datetime) -> IsAfterMatcher:
    """Matcher for a ``datetime`` strictly after ``other`` (a non-comparable value never matches)."""
    return IsAfterMatcher(other)

contains staticmethod

contains(*items: _Item) -> Matcher[Iterable[_Item]]

Matcher for a collection containing every one of items.

The spec spelling of contains(), with the same rules: a mapping is searched by key, and a matcher among the items is satisfied by any element.

Examples:

Usage:

assert_that(payload).matches_structure({"tags": match.contains("beta")})
assert_that(rows).satisfies(match.contains(match.greater_than(100)))
Source code in assertpy2/matchers.py
@staticmethod
def contains(*items: _Item) -> Matcher[Iterable[_Item]]:
    """Matcher for a collection containing every one of ``items``.

    The spec spelling of [`contains()`][assertpy2.contains.ContainsMixin.contains], with the same
    rules: a mapping is searched by key, and a matcher among the items is satisfied by any element.

    Examples:
        Usage:

            assert_that(payload).matches_structure({"tags": match.contains("beta")})
            assert_that(rows).satisfies(match.contains(match.greater_than(100)))
    """
    return ContainsMatcher(*items)

contains_only staticmethod

contains_only(*items: _Item) -> Matcher[Iterable[_Item]]

Matcher for a collection holding these items and nothing else.

The spec spelling of contains_only().

Source code in assertpy2/matchers.py
@staticmethod
def contains_only(*items: _Item) -> Matcher[Iterable[_Item]]:
    """Matcher for a collection holding these items and nothing else.

    The spec spelling of [`contains_only()`][assertpy2.contains.ContainsMixin.contains_only].
    """
    return ContainsOnlyMatcher(*items)

is_subset_of staticmethod

is_subset_of(
    superset: Iterable[_Item],
) -> Matcher[Iterable[_Item]]
is_subset_of(*superset: _Item) -> Matcher[Iterable[_Item]]

Matcher for a collection whose items all appear in superset.

The spec spelling of is_subset_of(). Takes the superset either as one collection or as loose items, which is why it is overloaded: read off a single union, a checker cannot tell [1, 2] the collection from [1, 2] the item.

An ordinary collection stays a live view of itself, the way equal_to keeps its expected value; a one-shot iterator is drained when the matcher is built, since it could not answer a second time otherwise. Handing in an endless iterator therefore never returns.

Source code in assertpy2/matchers.py
@staticmethod
def is_subset_of(*superset: object) -> Matcher[Iterable[Any]]:
    """Matcher for a collection whose items all appear in ``superset``.

    The spec spelling of [`is_subset_of()`][assertpy2.collection.CollectionMixin.is_subset_of].
    Takes the superset either as one collection or as loose items, which is why it is overloaded:
    read off a single union, a checker cannot tell `[1, 2]` the collection from `[1, 2]` the item.

    An ordinary collection stays a live view of itself, the way ``equal_to`` keeps its expected
    value; a one-shot iterator is drained when the matcher is built, since it could not answer a
    second time otherwise.  Handing in an endless iterator therefore never returns.
    """
    return IsSubsetOfMatcher(*superset)

is_sorted staticmethod

is_sorted(
    key: Callable[[Any], Any] | None = None,
    reverse: bool = False,
) -> Matcher[Iterable[Any]]

Matcher for a collection in order, optionally by key and optionally reversed.

The spec spelling of is_sorted().

Source code in assertpy2/matchers.py
@staticmethod
def is_sorted(key: Callable[[Any], Any] | None = None, reverse: bool = False) -> Matcher[Iterable[Any]]:
    """Matcher for a collection in order, optionally by ``key`` and optionally reversed.

    The spec spelling of [`is_sorted()`][assertpy2.collection.CollectionMixin.is_sorted].
    """
    return IsSortedMatcher(key, reverse)

each_item staticmethod

each_item(matcher: Matcher[Any]) -> EachMatcher

Matcher for an iterable whose every item matches matcher.

Parameters:

Name Type Description Default
matcher Matcher[Any]

the matcher each item of the iterable must satisfy; a non-iterable value never matches

required
Source code in assertpy2/matchers.py
@staticmethod
def each_item(matcher: Matcher[Any]) -> EachMatcher:
    """Matcher for an iterable whose every item matches ``matcher``.

    Args:
        matcher: the matcher each item of the iterable must satisfy; a non-iterable
            value never matches
    """
    return EachMatcher(matcher)

structure staticmethod

structure(spec: dict[Any, Any]) -> StructureMatcher

Matcher for a dict matching spec.

Parameters:

Name Type Description Default
spec dict[Any, Any]

dict whose values are matchers, raw values (compared with ==), or nested dict specs. Keys present in the value but absent from the spec are ignored.

required

Examples:

Usage:

assert_that(user).satisfies(
    match.structure({"id": match.is_instance_of(int), "name": "Alice"})
)
Source code in assertpy2/matchers.py
@staticmethod
def structure(spec: dict[Any, Any]) -> StructureMatcher:
    """Matcher for a dict matching ``spec``.

    Args:
        spec: dict whose values are matchers, raw values (compared with ``==``),
            or nested dict specs. Keys present in the value but absent from the spec are ignored.

    Examples:
        Usage:

            assert_that(user).satisfies(
                match.structure({"id": match.is_instance_of(int), "name": "Alice"})
            )
    """
    return StructureMatcher(spec)

BaseMatcher

Abstract base for all matchers with operator support.

A subclass implements either matches() or evaluate(), and gets the other one from here. matches() is the cheap primitive: it is what == calls, and matchers are used as dict values in matches_structure and as snapshot placeholders, so a comparison must not have to build a result object. evaluate() is the whole answer, for a caller that would otherwise ask the same value three questions in a row.

matches

matches(value: Any) -> bool
Source code in assertpy2/_matcher_impls.py
def matches(self, value: Any) -> bool:
    if type(self).evaluate is BaseMatcher.evaluate:
        raise NotImplementedError("a matcher must implement matches() or evaluate()")
    return self.evaluate(value).matched

evaluate

evaluate(value: Any) -> MatchResult

The verdict, the requirement and the reason, from one look at value.

The default composes the three older methods, so a matcher written before this existed answers it without changing. Overriding it is for a matcher whose reason costs what the verdict already paid for: the alternative is matches() and describe_mismatch() walking the same value twice, which is how a matcher over a one-shot iterator used to name the wrong element.

Source code in assertpy2/_matcher_impls.py
def evaluate(self, value: Any) -> MatchResult:
    """The verdict, the requirement and the reason, from one look at *value*.

    The default composes the three older methods, so a matcher written before this existed answers
    it without changing.  Overriding it is for a matcher whose reason costs what the verdict already
    paid for: the alternative is `matches()` and `describe_mismatch()` walking the same value twice,
    which is how a matcher over a one-shot iterator used to name the wrong element.
    """
    if type(self).matches is BaseMatcher.matches:
        raise NotImplementedError("a matcher must implement matches() or evaluate()")
    if verdict(self.matches(value), subject="the matcher"):
        return MatchResult(matched=True, description=self.describe())
    return _refused(self, value)

describe

describe() -> str
Source code in assertpy2/_matcher_impls.py
def describe(self) -> str:
    raise NotImplementedError

describe_mismatch

describe_mismatch(value: Any) -> str
Source code in assertpy2/_matcher_impls.py
def describe_mismatch(self, value: Any) -> str:
    return f"was <{_safe_str(value)}>"

MatchResult dataclass

MatchResult(
    *,
    matched: bool,
    description: str,
    mismatch: str = "",
    diff: DiffResult | None = None,
)

What a matcher decided about one value, in one object instead of three calls.

Deliberately not an AssertionOutcome, which is the record of a failed assertion. A matcher is asked about every leaf of a structure and about every element of a collection, so its result has to stay cheap: four fields, no location, no group, nothing that has to be computed before it is known whether anyone will read it.

matched instance-attribute

matched: bool

Whether the value matched.

description instance-attribute

description: str

What the matcher requires, in the words it uses in a failure message: a positive value.

mismatch class-attribute instance-attribute

mismatch: str = ''

Why this value did not match, in the words a failure message continues with: was <-1>.

Empty when it matched. There is nothing to say about a value that satisfied the matcher, and the text would have to be invented.

diff class-attribute instance-attribute

diff: DiffResult | None = None

A structured diff, from a matcher that compares rather than tests: equality has one, is_odd does not.

Matcher

Protocol for composable matcher objects, parameterised by the value they judge.

Contravariant, because a matcher consumes the value: one written for object answers about an int, and one written for str does not. That is the direction that makes satisfies(match.starts_with("a")) on an int a type error while keeping match.is_none() usable anywhere.

Written without a PEP 696 default on purpose: that spelling needs typing_extensions on everything before 3.13, and this package declares it only for 3.10. A bare Matcher in an annotation is read as Matcher[Any] by all four checkers anyway, so existing annotations keep their meaning.

register_matcher

register_matcher(
    name: str, *, override: bool = False
) -> Callable[
    [Callable[..., BaseMatcher]], Callable[..., BaseMatcher]
]

Register a custom matcher factory on the match namespace.

A name already taken by another custom matcher is refused unless override says otherwise, so two libraries registering has_status find out at import rather than by whichever imported last quietly winning.

A name already taken by a built-in matcher is refused outright, and override cannot lift it. match.equal_to resolves through ordinary attribute lookup, which never reaches this registry, so such a registration would not lose an argument - it would simply never run.

Parameters:

Name Type Description Default
name str

the name to register on match (e.g. "is_valid_email")

required
override bool

replace an existing custom matcher of the same name instead of refusing

False

Returns:

Type Description
Callable[[Callable[..., BaseMatcher]], Callable[..., BaseMatcher]]

A decorator that registers the wrapped function and returns it unchanged.

Raises:

Type Description
TypeError

if name is not a string, or the decorated object is not callable

ValueError

if name is not an identifier, names a built-in matcher, or names a custom one already registered and override is false

Examples:

Register a simple matcher:

@register_matcher("is_valid_email")
def is_valid_email():
    return match.matches_regex(r"^[\w.-]+@[\w.-]+\.\w+$")

assert_that(email).satisfies(match.is_valid_email())

Register a parametrised matcher:

@register_matcher("has_status")
def has_status(expected: str):
    return match.has_property("status", match.equal_to(expected))

assert_that(order).satisfies(match.has_status("active"))
Source code in assertpy2/matchers.py
def register_matcher(
    name: str, *, override: bool = False
) -> Callable[[Callable[..., BaseMatcher]], Callable[..., BaseMatcher]]:
    """Register a custom matcher factory on the ``match`` namespace.

    A name already taken by another custom matcher is refused unless ``override`` says otherwise, so
    two libraries registering ``has_status`` find out at import rather than by whichever imported
    last quietly winning.

    A name already taken by a *built-in* matcher is refused outright, and ``override`` cannot lift it.
    ``match.equal_to`` resolves through ordinary attribute lookup, which never reaches this registry,
    so such a registration would not lose an argument - it would simply never run.

    Args:
        name: the name to register on ``match`` (e.g. ``"is_valid_email"``)
        override: replace an existing custom matcher of the same name instead of refusing

    Returns:
        A decorator that registers the wrapped function and returns it unchanged.

    Raises:
        TypeError: if ``name`` is not a string, or the decorated object is not callable
        ValueError: if ``name`` is not an identifier, names a built-in matcher, or names a custom one
            already registered and ``override`` is false

    Examples:
        Register a simple matcher:

            @register_matcher("is_valid_email")
            def is_valid_email():
                return match.matches_regex(r"^[\\w.-]+@[\\w.-]+\\.\\w+$")

            assert_that(email).satisfies(match.is_valid_email())

        Register a parametrised matcher:

            @register_matcher("has_status")
            def has_status(expected: str):
                return match.has_property("status", match.equal_to(expected))

            assert_that(order).satisfies(match.has_status("active"))
    """
    if not isinstance(name, str):
        refuse(name, "a string", subject=argument("name"))
    if not name.isidentifier():
        raise ValueError(f"name must be a valid Python identifier, got {name!r}")
    if hasattr(_MatchNamespace, name):
        raise ValueError(
            f"match.{name} is a built-in matcher, and attribute lookup reaches it before this "
            f"registry: a custom {name!r} would never be called. Register it under another name."
        )

    def decorator(func: Callable[..., BaseMatcher]) -> Callable[..., BaseMatcher]:
        if not callable(func):
            refuse(func, "callable", subject=argument("func"))
        with _custom_matchers_lock:
            # a module imported twice, or a per-module fixture, is not two libraries fighting over one name
            clash = name in _custom_matchers and not is_same_implementation(_custom_matchers[name], func)
            if clash and not override:
                raise ValueError(
                    f"a custom matcher named {name!r} is already registered; pass override=True to "
                    f"replace it, or unregister_matcher({name!r}) first"
                )
            _custom_matchers[name] = func
        return func

    return decorator

unregister_matcher

unregister_matcher(name: str) -> None

Remove a previously registered custom matcher.

Parameters:

Name Type Description Default
name str

the matcher name to remove

required

Raises:

Type Description
KeyError

if the name is not registered

Source code in assertpy2/matchers.py
def unregister_matcher(name: str) -> None:
    """Remove a previously registered custom matcher.

    Args:
        name: the matcher name to remove

    Raises:
        KeyError: if the name is not registered
    """
    with _custom_matchers_lock:
        if name not in _custom_matchers:
            raise KeyError(f"no custom matcher registered with name {name!r}")
        del _custom_matchers[name]

clear_custom_matchers

clear_custom_matchers() -> None

Remove all registered custom matchers.

Source code in assertpy2/matchers.py
def clear_custom_matchers() -> None:
    """Remove all registered custom matchers."""
    with _custom_matchers_lock:
        _custom_matchers.clear()