Module pyracmon.testing.util

Expand source code
from collections.abc import Callable
from contextvars import ContextVar
from datetime import datetime, date, time, timedelta
from typing import Any, Optional
from ..config import PyracmonConfiguration, default_config, contextualConfiguration


config: ContextVar[PyracmonConfiguration] = ContextVar('config', default=contextualConfiguration(lambda: config, default_config()))


def default_test_config() -> PyracmonConfiguration:
    return config.get()


class Matcher:
    def __init__(self):
        self.invert = False

    def __invert__(self):
        self.invert = True
        return self

    def __and__(self, another):
        return CompositeMatcher(self, another, True)

    def __or__(self, another):
        return CompositeMatcher(self, another, False)

    def match(self, actual):
        return self._match(actual) ^ self.invert

    def _match(self, actual):
        raise NotImplementedError()


class CompositeMatcher(Matcher):
    def __init__(self, m1, m2, and_=True):
        super().__init__()
        self.m1 = m1
        self.m2 = m2
        self.and_ = and_

    def _match(self, actual):
        if self.and_:
            return self.m1.match(actual) and self.m2.match(actual)
        else:
            return self.m1.match(actual) or self.m2.match(actual)


class Near(Matcher):
    def __init__(self, expected, negative=None, positive=None, **kwargs):
        super().__init__()
        self.expected = expected
        self.negative = negative
        self.positive = positive
        self.kwargs = kwargs

    def _margin(self, t):
        neg = self.negative or 0
        pos = self.positive or 0

        if issubclass(t, (int, float)):
            return (self.expected + neg, self.expected + pos)
        elif issubclass(t, (datetime, date)):
            delta_args = {k:v for k, v in self.kwargs.items() if k in {
                'weeks', 'days', 'hours', 'minutes', 'seconds', 'milliseconds', 'microseconds',
            }}
            delta = timedelta(**delta_args)
            return (self.expected + neg * delta, self.expected + pos * delta)
        else:
            raise ValueError(f"Margin for {t} is not supported.")

    def _match(self, actual):
        if self.negative is None and self.positive is None:
            return actual == self.expected
        else:
            low, high = self._margin(type(actual))
            return low <= actual and actual <= high


def near(expected: Any, negative: Optional[Any] = None, positive: Optional[Any] = None, **kwargs: Any) -> Matcher:
    """
    Creates a matcher to check actual value is in a range.

    Comparison between the value and given range depends on its type.

    Args:
        expected: Expected value.
        negative: Margin of negative direction.
        positive: Margin of positive direction.
        kwargs: Keyword arguments to create marginal values.
    Returns:
        Created matcher.
    """
    if isinstance(expected, datetime):
        if all(k not in kwargs for k in ('weeks', 'days', 'hours', 'minutes', 'seconds', 'milliseconds', 'microseconds')):
            kwargs.update(**default_test_config().timedelta_unit)
    return Near(expected, negative, positive, **kwargs)


class let(Matcher):
    def __init__(self, pred: Callable[[Any], bool]) -> None:
        """
        Creates a matcher which applies the predicate function to actual value and checks its returning value is ``True`` .

        Args:
            pred: A predicate function.
        Returns:
            Created matcher.
        """
        super().__init__()
        self.pred = pred

    def _match(self, actual):
        return self.pred(actual)


class one_of(Matcher):
    def __init__(self, *candidates) -> None:
        """
        Creates a matcher which checks whether the actual value matches one of candidate values.

        Args:
            candidates: Candidate values.
        Returns:
            Created matcher.
        """
        super().__init__()
        self.candidates = candidates

    def _match(self, actual):
        for c in self.candidates:
            if isinstance(c, Matcher):
                if c.match(actual):
                    return True
            elif c == actual:
                return True
        return False

Functions

def default_test_config() ‑> PyracmonConfiguration
Expand source code
def default_test_config() -> PyracmonConfiguration:
    return config.get()
def near(expected: Any, negative: Optional[Any] = None, positive: Optional[Any] = None, **kwargs: Any) ‑> Matcher

Creates a matcher to check actual value is in a range.

Comparison between the value and given range depends on its type.

Args

expected
Expected value.
negative
Margin of negative direction.
positive
Margin of positive direction.
kwargs
Keyword arguments to create marginal values.

Returns

Created matcher.

Expand source code
def near(expected: Any, negative: Optional[Any] = None, positive: Optional[Any] = None, **kwargs: Any) -> Matcher:
    """
    Creates a matcher to check actual value is in a range.

    Comparison between the value and given range depends on its type.

    Args:
        expected: Expected value.
        negative: Margin of negative direction.
        positive: Margin of positive direction.
        kwargs: Keyword arguments to create marginal values.
    Returns:
        Created matcher.
    """
    if isinstance(expected, datetime):
        if all(k not in kwargs for k in ('weeks', 'days', 'hours', 'minutes', 'seconds', 'milliseconds', 'microseconds')):
            kwargs.update(**default_test_config().timedelta_unit)
    return Near(expected, negative, positive, **kwargs)

Classes

class CompositeMatcher (m1, m2, and_=True)
Expand source code
class CompositeMatcher(Matcher):
    def __init__(self, m1, m2, and_=True):
        super().__init__()
        self.m1 = m1
        self.m2 = m2
        self.and_ = and_

    def _match(self, actual):
        if self.and_:
            return self.m1.match(actual) and self.m2.match(actual)
        else:
            return self.m1.match(actual) or self.m2.match(actual)

Ancestors

class Matcher
Expand source code
class Matcher:
    def __init__(self):
        self.invert = False

    def __invert__(self):
        self.invert = True
        return self

    def __and__(self, another):
        return CompositeMatcher(self, another, True)

    def __or__(self, another):
        return CompositeMatcher(self, another, False)

    def match(self, actual):
        return self._match(actual) ^ self.invert

    def _match(self, actual):
        raise NotImplementedError()

Subclasses

Methods

def match(self, actual)
Expand source code
def match(self, actual):
    return self._match(actual) ^ self.invert
class Near (expected, negative=None, positive=None, **kwargs)
Expand source code
class Near(Matcher):
    def __init__(self, expected, negative=None, positive=None, **kwargs):
        super().__init__()
        self.expected = expected
        self.negative = negative
        self.positive = positive
        self.kwargs = kwargs

    def _margin(self, t):
        neg = self.negative or 0
        pos = self.positive or 0

        if issubclass(t, (int, float)):
            return (self.expected + neg, self.expected + pos)
        elif issubclass(t, (datetime, date)):
            delta_args = {k:v for k, v in self.kwargs.items() if k in {
                'weeks', 'days', 'hours', 'minutes', 'seconds', 'milliseconds', 'microseconds',
            }}
            delta = timedelta(**delta_args)
            return (self.expected + neg * delta, self.expected + pos * delta)
        else:
            raise ValueError(f"Margin for {t} is not supported.")

    def _match(self, actual):
        if self.negative is None and self.positive is None:
            return actual == self.expected
        else:
            low, high = self._margin(type(actual))
            return low <= actual and actual <= high

Ancestors

class let (pred: collections.abc.Callable[[typing.Any], bool])

Creates a matcher which applies the predicate function to actual value and checks its returning value is True .

Args

pred
A predicate function.

Returns

Created matcher.

Expand source code
class let(Matcher):
    def __init__(self, pred: Callable[[Any], bool]) -> None:
        """
        Creates a matcher which applies the predicate function to actual value and checks its returning value is ``True`` .

        Args:
            pred: A predicate function.
        Returns:
            Created matcher.
        """
        super().__init__()
        self.pred = pred

    def _match(self, actual):
        return self.pred(actual)

Ancestors

class one_of (*candidates)

Creates a matcher which checks whether the actual value matches one of candidate values.

Args

candidates
Candidate values.

Returns

Created matcher.

Expand source code
class one_of(Matcher):
    def __init__(self, *candidates) -> None:
        """
        Creates a matcher which checks whether the actual value matches one of candidate values.

        Args:
            candidates: Candidate values.
        Returns:
            Created matcher.
        """
        super().__init__()
        self.candidates = candidates

    def _match(self, actual):
        for c in self.candidates:
            if isinstance(c, Matcher):
                if c.match(actual):
                    return True
            elif c == actual:
                return True
        return False

Ancestors