Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 11 additions & 2 deletions precisely/base.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,14 @@
class Matcher(object):
pass
from abc import ABC, abstractmethod
from .results import Result

class Matcher(ABC):
@abstractmethod
def match(self, actual) -> Result:
pass

@abstractmethod
def describe(self) -> str:
pass


def is_matcher(value):
Expand Down
10 changes: 5 additions & 5 deletions precisely/comparison_matchers.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import operator

from .base import Matcher
from .results import matched, unmatched
from .results import matched, unmatched, Result


def contains_string(value):
Expand Down Expand Up @@ -38,13 +38,13 @@ def __init__(self, operator, operator_description, value):
self._operator_description = operator_description
self._value = value

def match(self, actual):
def match(self, actual) -> Result:
if self._operator(actual, self._value):
return matched()
else:
return unmatched("was {0!r}".format(actual))

def describe(self):
def describe(self) -> str:
return "{0} {1!r}".format(self._operator_description, self._value)


Expand All @@ -57,12 +57,12 @@ def __init__(self, value, delta):
self._value = value
self._delta = delta

def match(self, actual):
def match(self, actual) -> Result:
difference = abs(self._value - actual)
if difference > self._delta:
return unmatched("was {0!r} ({1!r} away from {2!r})".format(actual, difference, self._value))
else:
return matched()

def describe(self):
def describe(self) -> str:
return "close to {0!r} +/- {1!r}".format(self._value, self._delta)
22 changes: 11 additions & 11 deletions precisely/core_matchers.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
from .base import Matcher
from .results import matched, unmatched, indented_list
from .results import matched, unmatched, indented_list, Result


def equal_to(value):
Expand All @@ -10,21 +10,21 @@ class EqualToMatcher(Matcher):
def __init__(self, value):
self._value = value

def match(self, actual):
def match(self, actual) -> Result:
if self._value == actual:
return matched()
else:
return unmatched("was {0!r}".format(actual))

def describe(self):
def describe(self) -> str:
return repr(self._value)


class AnyThingMatcher(Matcher):
def match(self, actual):
def match(self, actual) -> Result:
return matched()

def describe(self):
def describe(self) -> str:
return "anything"


Expand All @@ -38,15 +38,15 @@ class AllOfMatcher(Matcher):
def __init__(self, matchers):
self._matchers = matchers

def match(self, actual):
def match(self, actual) -> Result:
for matcher in self._matchers:
result = matcher.match(actual)
if not result.is_match:
return result

return matched()

def describe(self):
def describe(self) -> str:
return "all of:{0}".format(indented_list(
matcher.describe()
for matcher in self._matchers
Expand All @@ -60,7 +60,7 @@ class AnyOfMatcher(Matcher):
def __init__(self, matchers):
self._matchers = matchers

def match(self, actual):
def match(self, actual) -> Result:
results = []
for matcher in self._matchers:
result = matcher.match(actual)
Expand All @@ -74,7 +74,7 @@ def match(self, actual):
for result, matcher in zip(results, self._matchers)
)))

def describe(self):
def describe(self) -> str:
return "any of:{0}".format(indented_list(
matcher.describe()
for matcher in self._matchers
Expand All @@ -88,12 +88,12 @@ class NotMatcher(Matcher):
def __init__(self, matcher):
self._matcher = matcher

def match(self, actual):
def match(self, actual) -> Result:
result = self._matcher.match(actual)
if result.is_match:
return unmatched("matched: {0}".format(self._matcher.describe()))
else:
return matched()

def describe(self):
def describe(self) -> str:
return "not: {0}".format(self._matcher.describe())
6 changes: 3 additions & 3 deletions precisely/feature_matchers.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
from .base import Matcher
from .results import matched, unmatched
from .results import matched, unmatched, Result
from .coercion import to_matcher


Expand All @@ -12,15 +12,15 @@ def __init__(self, name, extract, matcher):
self._extract = extract
self._matcher = matcher

def match(self, actual):
def match(self, actual) -> Result:
actual_feature = self._extract(actual)
feature_result = self._matcher.match(actual_feature)
if feature_result.is_match:
return matched()
else:
return unmatched(self._description(feature_result.explanation))

def describe(self):
def describe(self) -> str:
return self._description(self._matcher.describe())

def _description(self, value):
Expand Down
6 changes: 3 additions & 3 deletions precisely/function_matchers.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import traceback

from .base import Matcher
from .results import matched, unmatched
from .results import matched, unmatched, Result


def raises(exception_matcher):
Expand All @@ -12,7 +12,7 @@ class RaisesMatcher(Matcher):
def __init__(self, exception_matcher):
self._exception_matcher = exception_matcher

def match(self, actual):
def match(self, actual) -> Result:
if not callable(actual):
return unmatched("was not callable")

Expand All @@ -30,5 +30,5 @@ def match(self, actual):

return unmatched("did not raise exception")

def describe(self):
def describe(self) -> str:
return "a callable raising: {0}".format(self._exception_matcher.describe())
31 changes: 19 additions & 12 deletions precisely/iterable_matchers.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
try:
from itertools import zip_longest
except ImportError:
from itertools import izip_longest as zip_longest
# For Python 2
from itertools import izip_longest as zip_longest # type: ignore # no-redef

from .base import Matcher
from .results import matched, unmatched, indented_list, indexed_indented_list, Result
Expand All @@ -16,7 +17,7 @@ class ContainsExactlyMatcher(Matcher):
def __init__(self, matchers):
self._matchers = matchers

def match(self, actual):
def match(self, actual) -> Result:
values = _to_list_or_mismatch(actual)

if isinstance(values, Result):
Expand All @@ -31,7 +32,7 @@ def match(self, actual):
return result
return matches.match_remaining()

def describe(self):
def describe(self) -> str:
elements_description = indented_list(
matcher.describe()
for matcher in self._matchers
Expand All @@ -58,7 +59,7 @@ class IncludesMatcher(Matcher):
def __init__(self, matchers):
self._matchers = matchers

def match(self, actual):
def match(self, actual) -> Result:
values = _to_list_or_mismatch(actual)

if isinstance(values, Result):
Expand All @@ -73,7 +74,7 @@ def match(self, actual):
return result
return matched()

def describe(self):
def describe(self) -> str:
return "iterable including elements:{0}".format(indented_list(
matcher.describe()
for matcher in self._matchers
Expand Down Expand Up @@ -118,14 +119,20 @@ def match_remaining(self):
def is_sequence(*matchers):
return IsSequenceMatcher([to_matcher(matcher) for matcher in matchers])


class IsSequenceMatcher(Matcher):
_missing = object()
class _MissingMatcher(Matcher):
def match(self, actual) -> Result:
return unmatched("miss matching in _MissingMatcher")

def __init__(self, matchers):
def describe(self) -> str:
return "miss matching in _MissingMatcher"

_missing = _MissingMatcher()

def __init__(self, matchers: list[Matcher]):
self._matchers = matchers

def match(self, actual):
def match(self, actual) -> Result:
values = _to_list_or_mismatch(actual)

if isinstance(values, Result):
Expand All @@ -150,7 +157,7 @@ def match(self, actual):
else:
return matched()

def describe(self):
def describe(self) -> str:
if len(self._matchers) == 0:
return _empty_iterable_description
else:
Expand All @@ -169,7 +176,7 @@ class AllElementsMatcher(Matcher):
def __init__(self, matcher):
self._element_matcher = matcher

def match(self, actual):
def match(self, actual) -> Result:
values = _to_list_or_mismatch(actual)

if isinstance(values, Result):
Expand All @@ -182,7 +189,7 @@ def match(self, actual):

return matched()

def describe(self):
def describe(self) -> str:
return "all elements of iterable match: {0}".format(self._element_matcher.describe())


Expand Down
6 changes: 3 additions & 3 deletions precisely/mapping_matchers.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
from .base import Matcher
from .coercion import to_matcher
from .results import matched, unmatched, indented_list
from .results import matched, unmatched, indented_list, Result


def is_mapping(matchers):
Expand All @@ -23,7 +23,7 @@ def __init__(self, matchers, allow_extra):
self._allow_extra = allow_extra
self._matchers = matchers

def match(self, actual):
def match(self, actual) -> Result:
undefined = object()
for key, matcher in self._matchers.items():
value = actual.get(key, undefined)
Expand All @@ -41,7 +41,7 @@ def match(self, actual):

return matched()

def describe(self):
def describe(self) -> str:
items_description = indented_list(sorted(
"{0!r}: {1}".format(key, matcher.describe())
for key, matcher in self._matchers.items()
Expand Down
14 changes: 7 additions & 7 deletions precisely/object_matchers.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
from .base import Matcher
from .results import matched, unmatched, indented_list
from .results import matched, unmatched, indented_list, Result
from .coercion import to_matcher


Expand All @@ -11,7 +11,7 @@ def __init__(self, name, matcher):
self._name = name
self._matcher = matcher

def match(self, actual):
def match(self, actual) -> Result:
if not hasattr(actual, self._name):
return unmatched("was missing attribute {0}".format(self._name))
else:
Expand All @@ -22,7 +22,7 @@ def match(self, actual):
else:
return unmatched("attribute {0} {1}".format(self._name, property_result.explanation))

def describe(self):
def describe(self) -> str:
return "object with attribute {0}: {1}".format(self._name, self._matcher.describe())


Expand All @@ -46,14 +46,14 @@ def __init__(self, matchers):
for name, matcher in matchers
]

def match(self, actual):
def match(self, actual) -> Result:
for matcher in self._matchers:
result = matcher.match(actual)
if not result.is_match:
return result
return matched()

def describe(self):
def describe(self) -> str:
return "object with attributes:{0}".format(indented_list(
"{0}: {1}".format(matcher._name, matcher._matcher.describe())
for matcher in self._matchers
Expand All @@ -67,11 +67,11 @@ class IsInstance(Matcher):
def __init__(self, type_):
self._type = type_

def match(self, actual):
def match(self, actual) -> Result:
if isinstance(actual, self._type):
return matched()
else:
return unmatched("had type {0}".format(type(actual).__name__))

def describe(self):
def describe(self) -> str:
return "is instance of {0}".format(self._type.__name__)
2 changes: 1 addition & 1 deletion precisely/results.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import collections


Result = collections.namedtuple("_Result", ["is_match", "explanation"])
Result = collections.namedtuple("Result", ["is_match", "explanation"])


def matched():
Expand Down
Loading