Skip to content

Commit

Permalink
feat: implement callable positional argument #4
Browse files Browse the repository at this point in the history
  • Loading branch information
proofit404 committed Jan 2, 2021
1 parent 1e14e6f commit 60b349b
Show file tree
Hide file tree
Showing 4 changed files with 143 additions and 10 deletions.
15 changes: 15 additions & 0 deletions src/_primitives/argument.py
@@ -0,0 +1,15 @@
from _primitives.exceptions import PrimitiveError


class Argument:
"""An object appropriate to fake function arguments."""

def __init__(self, value):
self.value = value

def check(self, arg):
"""Check argument match."""
if arg != self.value:
raise PrimitiveError(
f"Called with argument {arg!r} while expected {self.value!r}"
)
65 changes: 60 additions & 5 deletions src/_primitives/callable.py
@@ -1,12 +1,67 @@
from _primitives.argument import Argument
from _primitives.exceptions import PrimitiveError


class Callable:
"""An object appropriate to fake functions and methods."""

def __init__(self, *args):
if args:
self.return_value = args[0]
else:
self.return_value = None
arguments, values = _arguments(args)
self.check = _Check(arguments)
self.return_value = _return_value(values)

def __call__(self):
def __call__(self, *args, **kwargs):
"""Return predefined value."""
self.check(args, kwargs)
return self.return_value


def _arguments(args):
arguments = []
values = []
for arg in args:
if isinstance(arg, Argument):
arguments.append(arg)
else:
values.append(arg)
return arguments, values


def _return_value(args):
if len(args) > 1:
raise PrimitiveError("'Callable' object should have only one return value")
elif args:
return args[0]


class _Check:
def __init__(self, arguments):
self.arguments = arguments

def __call__(self, args, kwargs):
if kwargs:
raise PrimitiveError("Positional arguments can not be called as keyword")
iterator = _Iterator(args)
for argument in self.arguments:
value = iterator.get()
argument.check(value)
iterator.last()


class _Iterator:
def __init__(self, args):
self.state = iter(args)

def get(self):
try:
return next(self.state)
except StopIteration:
raise PrimitiveError("Called with less arguments than expected")

def last(self):
try:
next(self.state)
except StopIteration:
pass
else:
raise PrimitiveError("Called with more arguments than expected")
4 changes: 2 additions & 2 deletions src/primitives/__init__.py
@@ -1,5 +1,5 @@
"""Fake objects designed with OOP in mind."""
from _primitives.argument import Argument
from _primitives.callable import Callable


__all__ = ["Callable"]
__all__ = ["Argument", "Callable"]
69 changes: 66 additions & 3 deletions tests/test_callable.py
@@ -1,12 +1,15 @@
"""Tests related to `primitives.Callable` function."""
import pytest

from primitives import Argument
from primitives import Callable
from primitives.exceptions import PrimitiveError # noqa: F401
from primitives.exceptions import PrimitiveError


def test_empty_callable_object():
def test_callable_object_return_null():
"""Empty `Callable` object should return null.
An object is callable when return value was not specified.
A callable object is empty when return value was not specified.
"""
func = Callable()
Expand All @@ -17,3 +20,63 @@ def test_callable_object_return_value():
"""`Callable` object should return value passed to it constructor."""
func = Callable("Hello, John")
assert func() == "Hello, John"


def test_callable_object_misconfigured():
"""`Callable` object should protect from multiple return values."""
with pytest.raises(PrimitiveError) as exc_info:
Callable(1, 2)

assert str(exc_info.value) == "'Callable' object should have only one return value"


def test_callable_object_null_argument():
"""`Callable` object should return null even if `Argument` was passed."""
func = Callable(Argument("John"))
assert func("John") is None

with pytest.raises(PrimitiveError) as exc_info:
func("Kate")

assert str(exc_info.value) == "Called with argument 'Kate' while expected 'John'"


def test_callable_object_return_value_argument():
"""`Callable`object should return value even if `Argument` was passed."""
func = Callable("Hello, John", Argument("John"))
assert func("John") == "Hello, John"

with pytest.raises(PrimitiveError) as exc_info:
func("Kate")

assert str(exc_info.value) == "Called with argument 'Kate' while expected 'John'"


def test_callable_object_positional_argument_prevent_keyword():
"""Positional `Argument` should provent usage of keyword argument."""
func = Callable(Argument("John"))

with pytest.raises(PrimitiveError) as exc_info:
func(a="John")

assert str(exc_info.value) == "Positional arguments can not be called as keyword"


def test_callable_object_positional_argument_not_enough():
"""Raise error if mock specified more arguments than user passed."""
func = Callable(Argument("John"), Argument("Kate"))

with pytest.raises(PrimitiveError) as exc_info:
func("John")

assert str(exc_info.value) == "Called with less arguments than expected"


def test_callable_object_positional_argument_overflow():
"""Raise error if user passed more arguments than mock specified."""
func = Callable(Argument("John"))

with pytest.raises(PrimitiveError) as exc_info:
func("John", 1)

assert str(exc_info.value) == "Called with more arguments than expected"

0 comments on commit 60b349b

Please sign in to comment.