예제 #1
0
파일: test_revision.py 프로젝트: dfee/forge
    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()
예제 #2
0
파일: test_revision.py 프로젝트: dfee/forge
    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
예제 #3
0
파일: test_utils.py 프로젝트: dfee/forge
 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)
예제 #4
0
파일: test_utils.py 프로젝트: dfee/forge
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)
예제 #5
0
파일: test_utils.py 프로젝트: dfee/forge
 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)
예제 #6
0
파일: test_utils.py 프로젝트: dfee/forge
 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)
예제 #7
0
파일: test_revision.py 프로젝트: dfee/forge
 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)
예제 #8
0
파일: test_revision.py 프로젝트: dfee/forge
    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
예제 #9
0
파일: test_revision.py 프로젝트: dfee/forge
    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
예제 #10
0
파일: test_revision.py 프로젝트: dfee/forge
    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,
        )
예제 #11
0
파일: test_revision.py 프로젝트: dfee/forge
    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)
예제 #12
0
파일: test_utils.py 프로젝트: dfee/forge
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)
예제 #13
0
    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)