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):
return self._expected in request.headers
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 :keyword:`True` if the matcher matches
the subject and :keyword:`False` if it does not.
:param subject: The target value of the expectation.
:rtype: a boolean
"""
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 ``not self._match(subject)``.
:param subject: The target value of the expectation.
:rtype: a boolean
"""
return not self._match(subject)
[docs] def _failure_message(self, subject):
"""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}'
With the passed `subject` and the result of calling the
:func:`_description` method.
:param subject: The target value of the expectation.
:rtype: a string
"""
return 'Expected {subject!r} to {description}'.format(
subject=subject, description=self._description(subject))
[docs] def _failure_message_negated(self, subject):
"""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} not to {description}'
:param subject: The target value of the expectation.
:rtype: a string
"""
return 'Expected {subject!r} not to {description}'.format(
subject=subject, description=self._description(subject))
[docs] def _description(self, subject):
"""This method receives the `subject` of the expectation and
returns a string with the description of the matcher to be
used in failure messages.
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.
:param subject: The target value of the expectation.
:rtype: a string
"""
if hasattr(self, '_expected'):
if hasattr(self._expected, '_description'):
expected = self._expected._description(None)
else:
expected = repr(self._expected)
return '{name} {expected}'.format(name=self._name,
expected=expected)
return self._name
@property
def _name(self):
return type(self).__name__.replace('_', ' ').strip()
[docs] def _match_value(self, matcher, value):
"""This method receives a :class:`Matcher` instance and a
`value to be matched` as first and second arguments respectively,
and returns :keyword:`True` or :keyword:`False` depending on
whether the `value` matches.
If the argument passed as `matcher` does not implements the
:class:`Matcher` interface then the :keyword:`equal` built-in
matcher is used.
Examples::
>>> self._match_value('foo', 'foo')
True
>>> self._match_value('foo', 'bar')
False
>>> self._match_value(match('\w+'), 'foo')
True
:param matcher: A matcher that will be used to match the given value.
:param value: A value to test if matches.
:rtype: bool
"""
if not hasattr(matcher, '_match'):
matcher = equal_matcher(matcher)
return matcher._match(value)
def __and__(self, other):
return _And(self, other)
def __or__(self, other):
return _Or(self, other)
from .built_in import equal as equal_matcher
class _And(Matcher):
def __init__(self, op1, op2):
self.op1 = op1
self.op2 = op2
def _match(self, subject):
return self.op1._match(subject) and self.op2._match(subject)
def _description(self, subject):
return '{} and {}'.format(self.op1._description(subject).replace(' and ', ', '),
self.op2._description(subject))
class _Or(Matcher):
def __init__(self, op1, op2):
self.op1 = op1
self.op2 = op2
def _match(self, subject):
return self.op1._match(subject) or self.op2._match(subject)
def _description(self, subject):
return '{} or {}'.format(self.op1._description(subject).replace(' or ', ', '),
self.op2._description(subject))