Skip to content

Module examples.example_objects

View Source
import asyncio

import inspect

from pprint import pformat

from typing import Any, Callable, get_type_hints

from pydantic import create_model

class NotDefined:

    """This exists to allow distinctly checking for a parameter not passed in

       vs. one that is passed in as None.

    """

    pass

class CallableExample:

    """Defines a single Example call against a callable."""

    __slots__ = ("args", "kwargs", "callable_object", "returns", "raises")

    def __init__(

        self, callable_object: Callable, args, kwargs, returns: Any = NotDefined, raises: Any = None

    ):

        self.args = args

        self.kwargs = kwargs

        self.callable_object = callable_object

        if raises and returns is not NotDefined:

            raise ValueError("Cannot specify both raises and returns on a single example.")

        self.returns = returns

        self.raises = raises

    def verify_signature(self, verify_types: bool = True):

        """Verifies that the example makes sense against the functions signature."""

        bound = inspect.signature(self.callable_object).bind(*self.args, **self.kwargs)

        annotations = get_type_hints(self.callable_object)

        if verify_types and annotations:

            test_type_hints = {}

            typed_example_values = {}

            for parameter_name, parameter_value in bound.arguments.items():

                type_hint = annotations.get(parameter_name, None)

                test_type_hints[parameter_name] = (type_hint, None)

                typed_example_values[parameter_name] = parameter_value

            if self.returns is not NotDefined and "return" in annotations:

                test_type_hints["return"] = annotations["return"]

                typed_example_values["returns"] = self.returns

            create_model(  # type: ignore

                getattr(self.callable_object, "__name__", "ExamplesModel"), **test_type_hints

            )(**typed_example_values)

    def use(self) -> Any:

        """Runs the given example, giving back the result returned from running the example call."""

        if inspect.iscoroutinefunction(self.callable_object):

            loop = asyncio.get_event_loop()

            call = self.callable_object(*self.args, **self.kwargs)

            if loop.is_running():

                return call  # pragma: no cover

            function = asyncio.ensure_future(call, loop=loop)

            loop.run_until_complete(function)

            return function.result()

        return self.callable_object(*self.args, **self.kwargs)

    def test(self, verify_return_type: bool = True):

        """Tests the given example, ensuring the return value matches that specified."""

        try:

            result = self.use()

        except BaseException as exception:

            if not self.raises:

                raise

            if (type(self.raises) == type and not isinstance(exception, self.raises)) or (

                type(self.raises) != type

                and (

                    not isinstance(exception, type(self.raises))

                    or self.raises.args != exception.args

                )

            ):

                raise AssertionError(

                    f"Example expected {repr(self.raises)} to be raised but "

                    f"instead {repr(exception)} was raised"

                )

            return

        if self.raises:

            raise AssertionError(

                f"Example expected {repr(self.raises)} to be raised "

                f"but instead {repr(result)} was returned"

            )

        elif self.returns is not NotDefined:

            if result != self.returns:

                raise AssertionError(

                    f"Example's expected return value of '{self.returns}' "

                    f"does not not match actual return value of `{result}`"

                )

        if verify_return_type:

            type_hints = get_type_hints(self.callable_object)

            if type_hints and "return" in type_hints:

                create_model(  # type: ignore

                    getattr(self.callable_object, "__name__", "ExamplesModel"),

                    returns=type_hints["return"],

                )(returns=result)

    def verify_and_test(self, verify_types: bool = True) -> None:

        self.verify_signature(verify_types=verify_types)

        self.test(verify_return_type=verify_types)

    def __str__(self):

        arg_str = ",\n    ".join(repr(arg) for arg in self.args)

        if self.kwargs:

            arg_str += ",\n    " if arg_str else ""

            arg_str += ",\n    ".join(

                f"{name}={repr(value)}" for name, value in self.kwargs.items()

            )

        call_str = f"{self.callable_object.__name__}(\n    {arg_str}\n)"

        if self.returns is not NotDefined:

            call_str += f"\n == \n{pformat(self.returns)}"

        elif self.raises:

            call_str += f"\nraises {pformat(self.raises)}"

        return call_str

    def __repr__(self):

        return f"Example:\n{str(self)}"

Classes

CallableExample
class (
    callable_object: Callable,
    args,
    kwargs,
    returns: Any = <class 'examples.example_objects.NotDefined'>,
    raises: Any = None
)

Defines a single Example call against a callable.

View Source
class CallableExample:

    """Defines a single Example call against a callable."""

    __slots__ = ("args", "kwargs", "callable_object", "returns", "raises")

    def __init__(

        self, callable_object: Callable, args, kwargs, returns: Any = NotDefined, raises: Any = None

    ):

        self.args = args

        self.kwargs = kwargs

        self.callable_object = callable_object

        if raises and returns is not NotDefined:

            raise ValueError("Cannot specify both raises and returns on a single example.")

        self.returns = returns

        self.raises = raises

    def verify_signature(self, verify_types: bool = True):

        """Verifies that the example makes sense against the functions signature."""

        bound = inspect.signature(self.callable_object).bind(*self.args, **self.kwargs)

        annotations = get_type_hints(self.callable_object)

        if verify_types and annotations:

            test_type_hints = {}

            typed_example_values = {}

            for parameter_name, parameter_value in bound.arguments.items():

                type_hint = annotations.get(parameter_name, None)

                test_type_hints[parameter_name] = (type_hint, None)

                typed_example_values[parameter_name] = parameter_value

            if self.returns is not NotDefined and "return" in annotations:

                test_type_hints["return"] = annotations["return"]

                typed_example_values["returns"] = self.returns

            create_model(  # type: ignore

                getattr(self.callable_object, "__name__", "ExamplesModel"), **test_type_hints

            )(**typed_example_values)

    def use(self) -> Any:

        """Runs the given example, giving back the result returned from running the example call."""

        if inspect.iscoroutinefunction(self.callable_object):

            loop = asyncio.get_event_loop()

            call = self.callable_object(*self.args, **self.kwargs)

            if loop.is_running():

                return call  # pragma: no cover

            function = asyncio.ensure_future(call, loop=loop)

            loop.run_until_complete(function)

            return function.result()

        return self.callable_object(*self.args, **self.kwargs)

    def test(self, verify_return_type: bool = True):

        """Tests the given example, ensuring the return value matches that specified."""

        try:

            result = self.use()

        except BaseException as exception:

            if not self.raises:

                raise

            if (type(self.raises) == type and not isinstance(exception, self.raises)) or (

                type(self.raises) != type

                and (

                    not isinstance(exception, type(self.raises))

                    or self.raises.args != exception.args

                )

            ):

                raise AssertionError(

                    f"Example expected {repr(self.raises)} to be raised but "

                    f"instead {repr(exception)} was raised"

                )

            return

        if self.raises:

            raise AssertionError(

                f"Example expected {repr(self.raises)} to be raised "

                f"but instead {repr(result)} was returned"

            )

        elif self.returns is not NotDefined:

            if result != self.returns:

                raise AssertionError(

                    f"Example's expected return value of '{self.returns}' "

                    f"does not not match actual return value of `{result}`"

                )

        if verify_return_type:

            type_hints = get_type_hints(self.callable_object)

            if type_hints and "return" in type_hints:

                create_model(  # type: ignore

                    getattr(self.callable_object, "__name__", "ExamplesModel"),

                    returns=type_hints["return"],

                )(returns=result)

    def verify_and_test(self, verify_types: bool = True) -> None:

        self.verify_signature(verify_types=verify_types)

        self.test(verify_return_type=verify_types)

    def __str__(self):

        arg_str = ",\n    ".join(repr(arg) for arg in self.args)

        if self.kwargs:

            arg_str += ",\n    " if arg_str else ""

            arg_str += ",\n    ".join(

                f"{name}={repr(value)}" for name, value in self.kwargs.items()

            )

        call_str = f"{self.callable_object.__name__}(\n    {arg_str}\n)"

        if self.returns is not NotDefined:

            call_str += f"\n == \n{pformat(self.returns)}"

        elif self.raises:

            call_str += f"\nraises {pformat(self.raises)}"

        return call_str

    def __repr__(self):

        return f"Example:\n{str(self)}"

NotDefined
class (
    /,
    *args,
    **kwargs
)

This exists to allow distinctly checking for a parameter not passed in vs. one that is passed in as None.

View Source
class NotDefined:

    """This exists to allow distinctly checking for a parameter not passed in

       vs. one that is passed in as None.

    """

    pass