Source code for expects.matchers

# -*- coding: utf-8 -*

"""
Introduction
------------

*Expects* can be `extended` by defining `new matchers`.
The :mod:`matchers` module contains the bases for building
custom matchers.

Tutorial
--------

The easiest way to define a new matcher is to extend the
:class:`Matcher` class and override the :func:`Matcher._match`
method.

For example, to define a matcher to check if a `request` object contains
a given header takes <10 lines of code::

    from expects.matchers import Matcher

    class have_header(Matcher):
        def __init__(self, expected):
            self._expected = expected

        def _match(self, request):
            if self._expected in request.headers:
                return True, ['header found']
            return False, ['header not found']

An then you only need to import the new defined matcher and write
your expectation::

    from expects import expect
    from my_custom_matchers import have_header

    expect(my_request).to(have_header('Content-Type'))

Advanced
--------

For more complex matchers you can override the :class:`Matcher`
methods in order to achieve the needed behavior.

"""


[docs] class Matcher(object): """The :class:`Matcher` class is the base class for all `Expects` matchers. It defines a set of methods to ease writting new matchers. """
[docs] def _match(self, subject): """This method will be called when the matcher is used in an expectation. It should be overwritten to implement the matcher logic. If not raises :class:`NotImplementedError`. Receives the expectation `subject` as the unique positional argument and should return a :keyword:`tuple` with the :keyword:`bool` result of the matcher and a :keyword:`list` of reasons for this result. If the matcher matches the subject then the boolean result should be :keyword:`True`. The reasons should be a list with 0 or more strings. :param subject: The target value of the expectation. :rtype: tuple (bool, [str]) """ raise NotImplementedError()
[docs] def _match_negated(self, subject): """Like :func:`_match` but will be called when used in a negated expectation. It can be used to implement a custom logic for negated expectations. By default returns the result of negating ``self._match(subject)``. :param subject: The target value of the expectation. :rtype: tuple (bool, [str]) """ result, reason = self._match(subject) return not result, reason
[docs] def _failure_message(self, subject, reasons): """This method will be called from an expectation `only` when the expectation is going to fail. It should return a string with the failure message. By default returns a failure message with the following format:: expected: {subject} to {description} but: {reasons} With the passed `subject` repr, this matcher repr as `description` and the passed `reasons` from the matcher result. :param subject: The target value of the expectation. :param reasons: A list of reasons that caused this matcher to fail. :type subject: a string :type reasons: list of strings :rtype: a string """ message = '\nexpected: {subject!r} to {matcher!r}'.format( subject=subject, matcher=self) if reasons: message += '\n but: {0}'.format('\n '.join(reasons)) return message
[docs] def _failure_message_negated(self, subject, reasons): """Like the :func:`_failure_message` method but will be called when a negated expectation is going to fail. It should return a string with the failure message for the negated expectation. By default returns a failure message with the following format:: expected: {subject} to {description} but: {reasons} :param subject: The target value of the expectation. :param reasons: A list of reasons that caused this matcher to fail. :type subject: a string :type reasons: list of strings :rtype: a string """ message = '\nexpected: {subject!r} not to {matcher!r}'.format( subject=subject, matcher=self) if reasons: message += '\n but: {0}'.format('\n '.join(reasons)) return message
def __repr__(self): """Returns a string with the description of the matcher. By default returns a string with the following format:: '{name} {expected}' Where `name` is based on the matcher class name and `expected` is the value passed to the constructor. :rtype: a string """ if hasattr(self, '_expected'): return '{name} {expected!r}'.format(name=self._name, expected=self._expected) return self._name @property def _name(self): return type(self).__name__.replace('_', ' ').strip() def __and__(self, other): return _And(self, other) def __or__(self, other): return _Or(self, other)
def default_matcher(value): if not isinstance(value, Matcher): return equal_matcher(value) return value
[docs] class _And(Matcher): def __init__(self, op1, op2): self.op1 = op1 self.op2 = op2
[docs] def _match(self, subject): result1, _ = self.op1._match(subject) result2, _ = self.op2._match(subject) return result1 and result2, []
def __repr__(self): return '{0} and {1}'.format(repr(self.op1).replace(' and ', ', '), repr(self.op2))
[docs] class _Or(Matcher): def __init__(self, op1, op2): self.op1 = op1 self.op2 = op2
[docs] def _match(self, subject): result1, _ = self.op1._match(subject) result2, _ = self.op2._match(subject) return result1 or result2, []
def __repr__(self): return '{0} or {1}'.format(repr(self.op1).replace(' or ', ', '), repr(self.op2))
from .built_in import equal as equal_matcher