def test__call__existing(self): """ Ensure ``__call__`` replaces the wrapper, and that a call to the wrapped func traverses only the new ``Mapper.__call__`` and the wrapped function; i.e. no double wrapping. """ rev = Revision() # pylint: disable=W0108, unnecessary-lambda func = lambda **kwargs: CallArguments(**kwargs) func2 = rev(func) f2mapper = func2.__mapper__ func3 = rev(func2) f3mapper = func3.__mapper__ assert func3 is not func2 assert func3.__wrapped__ is func2.__wrapped__ assert func3.__mapper__ is not func2.__mapper__ assert isinstance(f3mapper, Mapper) assert f3mapper is not f2mapper assert f3mapper.fsignature == f2mapper.fsignature func2.__mapper__ = Mock(side_effect=f2mapper) func3.__mapper__ = Mock(side_effect=f3mapper) call_args = CallArguments(b=1) assert func3(*call_args.args, **call_args.kwargs) == call_args func3.__mapper__.assert_called_once_with(**call_args.kwargs) func2.__mapper__.assert_not_called()
def test__call__params_mapped(self, from_kind, to_kind, vary_name): """ Ensure that call arguments are mapped from parameters of type: - POSITIONAL_ONLY - POSITIONAL_OR_KEYWORD - KEYWORD_ONLY to their interface counterparts as: - POSITIONAL_ONLY - POSITIONAL_OR_KEYWORD - KEYWORD_ONLY - VAR_KEYWORD with and without names being varied. """ from_name, to_name = ('p1', 'p1') if not vary_name else ('p1', 'p2') fsig = FSignature([FParameter(from_kind, from_name, to_name)]) func = lambda: None func.__signature__ = \ inspect.Signature([inspect.Parameter(to_name, to_kind)]) mapper = Mapper(fsig, func) call_args = CallArguments(**{from_name: 1}) \ if from_kind in (KEYWORD_ONLY, VAR_KEYWORD) \ else CallArguments(1) expected = CallArguments(**{to_name: 1}) \ if to_kind in (KEYWORD_ONLY, VAR_KEYWORD) \ else CallArguments(1) result = mapper(*call_args.args, **call_args.kwargs) assert result == expected
def test_from_bound_arguments(self): """ Ensure that ``inspect.BoundArguments`` ``args`` and ``kwargs`` are properly mapped to a new ``CallArguments`` instance. """ # pylint: disable=W0613, unused-argument def func(a, *, b): pass bound = inspect.signature(func).bind(a=1, b=2) # pylint: disable=E1101, no-member assert CallArguments.from_bound_arguments(bound) == \ CallArguments(1, b=2)
class TestCallArguments: def test_from_bound_arguments(self): """ Ensure that ``inspect.BoundArguments`` ``args`` and ``kwargs`` are properly mapped to a new ``CallArguments`` instance. """ # pylint: disable=W0613, unused-argument def func(a, *, b): pass bound = inspect.signature(func).bind(a=1, b=2) # pylint: disable=E1101, no-member assert CallArguments.from_bound_arguments(bound) == \ CallArguments(1, b=2) @pytest.mark.parametrize(('partial',), [(True,), (False,)]) @pytest.mark.parametrize(('call_args', 'incomplete'), [ pytest.param(CallArguments(1, b=2), False, id='complete'), pytest.param(CallArguments(), True, id='incomplete'), ]) def test_to_bound_arguments(self, call_args, partial, incomplete): """ Ensure that ``CallArguments`` ``args`` and ``kwargs`` are properly mapped to a new ``inspect.BoundArguments`` instance. """ # pylint: disable=W0613, unused-argument def func(a, *, b): pass sig = inspect.signature(func) if not partial and incomplete: with pytest.raises(TypeError) as excinfo: call_args.to_bound_arguments(sig, partial=partial) assert excinfo.value.args[0] == \ "missing a required argument: 'a'" return assert call_args.to_bound_arguments(sig, partial=partial) == \ sig.bind_partial(*call_args.args, **call_args.kwargs) @pytest.mark.parametrize(('args', 'kwargs', 'expected'), [ pytest.param((0,), {}, '0', id='args_only'), pytest.param((), {'a': 1}, 'a=1', id='kwargs_only'), pytest.param((0,), {'a': 1}, '0, a=1', id='args_and_kwargs'), pytest.param((), {}, '', id='neither_args_nor_kwargs'), ]) def test__repr__(self, args, kwargs, expected): """ Ensure that ``CallArguments.__repr__`` is a pretty print of ``args`` and ``kwargs``. """ assert repr(CallArguments(*args, **kwargs)) == \ '<CallArguments ({})>'.format(expected)
def test__repr__(self, args, kwargs, expected): """ Ensure that ``CallArguments.__repr__`` is a pretty print of ``args`` and ``kwargs``. """ assert repr(CallArguments(*args, **kwargs)) == \ '<CallArguments ({})>'.format(expected)
def test_callable(self): """ Ensure that callable's are viable arguments for ``to_`` """ func = lambda a, b=2, *args, c, d=4, **kwargs: None assert sort_arguments(func, dict(a=1, c=3, e=5), ('args1',)) == \ CallArguments(1, 2, 'args1', c=3, d=4, e=5)
def test__call__bound_injected(self): """ Ensure ``bound`` fparams are injected into the mapping. """ fsig = FSignature([forge.arg('bound', default=1, bound=True)]) func = lambda bound: bound mapper = Mapper(fsig, func) assert mapper() == CallArguments(1)
def test__call__vkw_param_mapped(self, vary_name): """ Ensure ``var-keyword`` params are directly mapped (w/ and w/o varied name) """ from_name, to_name = ('p1', 'p1') if not vary_name else ('p1', 'p2') fsig = FSignature([FParameter(VAR_KEYWORD, from_name, to_name)]) func = lambda: None func.__signature__ = \ inspect.Signature([inspect.Parameter(to_name, VAR_KEYWORD)]) mapper = Mapper(fsig, func) call_args = CallArguments(a=1, b=2, c=3) assert mapper(**call_args.kwargs) == call_args
def test__call__vpo_param_mapped(self, vary_name): """ Ensure ``var-positional`` params are directly mapped (w/ and w/o varied name) """ from_name, to_name = ('p1', 'p1') if not vary_name else ('p1', 'p2') fsig = FSignature([FParameter(VAR_POSITIONAL, from_name, to_name)]) func = lambda: None func.__signature__ = \ inspect.Signature([inspect.Parameter(to_name, VAR_POSITIONAL)]) mapper = Mapper(fsig, func) call_args = CallArguments(1, 2, 3) assert mapper(*call_args.args) == call_args
def test__call__not_existing(self, loop, as_coroutine): """ Ensure ``sign`` wrapper appropriately builds and sets ``__mapper__``, and that a call to the wrapped func traverses ``Mapper.__call__`` and the wrapped function. """ # pylint: disable=W0108, unnecessary-lambda rev = Revision() func = lambda *args, **kwargs: CallArguments(*args, **kwargs) if as_coroutine: func = asyncio.coroutine(func) func2 = rev(func) assert isinstance(func2.__mapper__, Mapper) assert isinstance(func2.__signature__, inspect.Signature) mapper = func2.__mapper__ assert mapper.callable == func2.__wrapped__ assert mapper.fsignature == FSignature([ forge.vpo('args'), forge.vkw('kwargs'), ]) assert mapper == Mapper(mapper.fsignature, func2.__wrapped__) func2.__mapper__ = Mock(side_effect=func2.__mapper__) call_args = CallArguments(0, a=1) result = func2(*call_args.args, **call_args.kwargs) if as_coroutine: result = loop.run_until_complete(result) assert result == call_args func2.__mapper__.assert_called_once_with( *call_args.args, **call_args.kwargs, )
def test__call__defaults_applied(self, from_kind): """ Ensure that defaults are applied to the underlying params (i.e. args passed): - POSITIONAL_ONLY - POSITIONAL_OR_KEYWORD - KEYWORD_ONLY """ from_param = self.make_param('a', from_kind, default=1) from_sig = inspect.Signature([from_param]) fsig = FSignature.from_native(from_sig) func = lambda: None func.__signature__ = \ inspect.Signature([inspect.Parameter('kwargs', VAR_KEYWORD)]) mapper = Mapper(fsig, func) assert mapper() == CallArguments(a=1)
class TestSortArguments: @pytest.mark.parametrize(('kind', 'named', 'unnamed', 'expected'), [ pytest.param( # func(param, /) POSITIONAL_ONLY, dict(param=1), None, CallArguments(1), id='positional-only', ), pytest.param( # func(param) POSITIONAL_OR_KEYWORD, dict(param=1), None, CallArguments(1), id='positional-or-keyword', ), pytest.param( # func(*param) VAR_POSITIONAL, None, (1,), CallArguments(1), id='var-positional', ), pytest.param( # func(*, param) KEYWORD_ONLY, dict(param=1), None, CallArguments(param=1), id='keyword-only', ), pytest.param( # func(**param) VAR_KEYWORD, dict(param=1), None, CallArguments(param=1), id='var-keyword', ), ]) def test_sorting(self, kind, named, unnamed, expected): """ Ensure that a named argument is appropriately sorted into a: - positional-only param - positional-or-keyword param - keyword-only param - var-keyword param """ to_ = inspect.Signature([inspect.Parameter('param', kind)]) result = sort_arguments(to_, named, unnamed) assert result == expected @pytest.mark.parametrize(('kind', 'expected'), [ pytest.param( # func(param=1, /) POSITIONAL_ONLY, CallArguments(1), id='positional-only', ), pytest.param( # func(param=1) POSITIONAL_OR_KEYWORD, CallArguments(1), id='positional-or-keyword', ), pytest.param( # func(*, param=1) KEYWORD_ONLY, CallArguments(param=1), id='keyword-only', ), ]) def test_sorting_with_defaults(self, kind, expected): """ Ensure that unsuplied named arguments use default values """ to_ = inspect.Signature([inspect.Parameter('param', kind, default=1)]) result = sort_arguments(to_) assert result == expected @pytest.mark.parametrize(('kind',), [ pytest.param(POSITIONAL_ONLY, id='positional-only'), pytest.param(POSITIONAL_OR_KEYWORD, id='positional-or-keyword'), pytest.param(KEYWORD_ONLY, id='keyword-only'), ]) def test_no_argument_for_non_default_param_raises(self, kind): """ Ensure that a non-default parameter must have an argument passed """ sig = inspect.Signature([inspect.Parameter('a', kind)]) with pytest.raises(ValueError) as excinfo: sort_arguments(sig) assert excinfo.value.args[0] == \ "Non-default parameter 'a' has no argument value" def test_extra_to_sig_without_vko_raises(self): """ Ensure a signature without a var-keyword parameter raises when extra arguments are supplied """ sig = inspect.Signature() with pytest.raises(TypeError) as excinfo: sort_arguments(sig, {'a': 1}) assert excinfo.value.args[0] == 'Cannot sort arguments (a)' def test_unnamaed_to_sig_without_vpo_raises(self): """ Ensure a signature without a var-positional parameter raises when a var-positional argument is supplied """ sig = inspect.Signature() with pytest.raises(TypeError) as excinfo: sort_arguments(sig, unnamed=(1,)) assert excinfo.value.args[0] == 'Cannot sort var-positional arguments' def test_callable(self): """ Ensure that callable's are viable arguments for ``to_`` """ func = lambda a, b=2, *args, c, d=4, **kwargs: None assert sort_arguments(func, dict(a=1, c=3, e=5), ('args1',)) == \ CallArguments(1, 2, 'args1', c=3, d=4, e=5)
def __call__(self, *args: typing.Any, **kwargs: typing.Any) -> CallArguments: """ Maps the arguments from the :paramref:`~forge.Mapper.public_signature` to the :paramref:`~forge.Mapper.private_signature`. Follows the strategy: #. bind the arguments to the :paramref:`~forge.Mapper.public_signature` #. partialy bind the :paramref:`~forge.Mapper.private_signature` #. identify the context argument (if one exists) from :class:`~forge.FParameter`s on the :class:`~forge.FSignature` #. iterate over the intersection of bound arguments and ``bound`` \ parameters on the :paramref:`.Mapper.fsignature` to the \ :paramref:`~forge.Mapper.private_signature` of the \ :paramref:`.Mapper.callable`, getting their transformed value by \ calling :meth:`~forge.FParameter.__call__` #. map the resulting value into the private_signature bound arguments #. generate and return a :class:`~forge._signature.CallArguments` from \ the private_signature bound arguments. :param args: the positional arguments to map :param kwargs: the keyword arguments to map :returns: transformed :paramref:`~forge.Mapper.__call__.args` and :paramref:`~forge.Mapper.__call__.kwargs` mapped from :paramref:`~forge.Mapper.public_signature` to :paramref:`~forge.Mapper.private_signature` """ try: public_ba = self.public_signature.bind(*args, **kwargs) except TypeError as exc: raise TypeError( '{callable_name}() {message}'.\ format( callable_name=self.callable.__name__, message=exc.args[0], ), ) public_ba.apply_defaults() private_ba = self.private_signature.bind_partial() private_ba.apply_defaults() ctx = self.get_context(public_ba.arguments) for from_name, from_param in self.fsignature.parameters.items(): from_val = public_ba.arguments.get(from_name, empty) to_name = self.parameter_map[from_name] to_param = self.private_signature.parameters[to_name] to_val = self.fsignature.parameters[from_name](ctx, from_val) if to_param.kind is FParameter.VAR_POSITIONAL: # e.g. f(*args) -> g(*args) private_ba.arguments[to_name] = to_val elif to_param.kind is FParameter.VAR_KEYWORD: if from_param.kind is FParameter.VAR_KEYWORD: # e.g. f(**kwargs) -> g(**kwargs) private_ba.arguments[to_name].update(to_val) else: # e.g. f(a) -> g(**kwargs) private_ba.arguments[to_name]\ [from_param.interface_name] = to_val else: # e.g. f(a) -> g(a) private_ba.arguments[to_name] = to_val return CallArguments.from_bound_arguments(private_ba)