def test_revise_args_order_preserved(self): """ Ensure that the var-positional arguments *aren't* re-ordered """ param_a = forge.arg('a') param_b = forge.arg('b') rev = synthesize(param_b, param_a) assert rev.revise(FSignature()) == FSignature([param_b, param_a])
def test_revise_kwargs_reordered(self): """ Ensure that the var-keyword arguments *are* re-ordered """ param_a = forge.arg('a') param_b = forge.arg('b') rev = synthesize(b=param_b, a=param_a) assert rev.revise(FSignature()) == FSignature([param_a, param_b])
def test_revise_args_precede_kwargs(self): """ Ensure that var-postional arguments precede var-keyword arguments """ param_a = forge.arg('a') param_b = forge.arg('b') rev = synthesize(param_b, a=param_a) assert rev.revise(FSignature()) == FSignature([param_b, param_a])
def test_revise_no_validation(self): """ Ensure no validation is performed on the revision """ rev = synthesize(forge.arg('b'), forge.pos('a')) assert rev.revise(FSignature()) == FSignature( [forge.arg('b'), forge.pos('a')], __validate_parameters__=False, )
def test_revise_no_validation(self): """ Ensure no validation is performed on the revision """ rev = insert(forge.arg('b'), index=0) fsig = FSignature([forge.pos('a')], __validate_parameters__=False) assert rev.revise(fsig) == FSignature( [forge.arg('b'), forge.pos('a')], __validate_parameters__=False, )
def test_revise_no_validation(self): """ Ensure no validation is performed on the revision """ rev = translocate('b', index=0) in_ = FSignature([forge.pos('a'), forge.arg('b')]) out_ = FSignature( [forge.arg('b'), forge.pos('a')], __validate_parameters__=False, ) assert rev.revise(in_) == out_
def test_revise_no_validation(self): """ Ensure no validation is performed on the revision """ rev = modify('b', kind=POSITIONAL_ONLY) in_ = FSignature([forge.arg('a'), forge.arg('b')]) out_ = FSignature( [forge.arg('a'), forge.pos('b')], __validate_parameters__=False, ) assert rev.revise(in_) == out_
def test_revise(self, in_, sortkey, expected): """ Ensure that parameter sorting: - doesn't validate the signature - by default sorts by (kind, has-default, name) - takes advantage of user-supplied sortkey """ rev = sort(sortkey) in_ = FSignature(in_, __validate_parameters__=False) expected = FSignature(expected, __validate_parameters__=False) assert rev.revise(in_) == expected
def test_revise(self, revision): """ Ensure that ``modify`` appropriately revises every attribute of a parameter. """ in_param = forge.pos('a') out_param = in_param.replace(**revision) assert in_param != out_param # ensure we've got a good test setup rev = modify('a', **revision) assert rev.revise(FSignature([in_param])) == FSignature([out_param])
def test_revise(self, selector): """ Ensure that ``replace`` accepts selector values; i.e. those passed to ``findparam``. """ new_param = forge.arg('new') old_param = forge.arg('old') in_ = FSignature([old_param]) out_ = FSignature([new_param]) rev = replace(selector, new_param) assert rev.revise(in_) == out_
def test_map_parameters_to_var_keyword(self, from_kind): """ Ensure the mapping **strategy** produced mapping *to* ``var-keyword`` - POSITIONAL_ONLY -> VAR_POSITIONAL (success) - POSTIIONAL_OR_KEYWORD -> VAR_POSITIONAL (success) - VAR_POSTIIONAL -> VAR_POSITIONAL (raises) - KEYWORD_ONLY -> VAR_POSITIONAL (success) - VAR_KEYWROD -> VAR_POSITIONAL (success) """ from_param = self.make_param('a', from_kind) from_sig = inspect.Signature([from_param]) fsig = FSignature.from_native(from_sig) to_param = self.make_param('kwargs', VAR_KEYWORD) to_sig = inspect.Signature([to_param]) expected_exc = None if from_param.kind is VAR_POSITIONAL: expected_exc = TypeError( "Missing requisite mapping from variable positional " "parameter 'a'") if expected_exc: with pytest.raises(type(expected_exc)) as excinfo: Mapper.map_parameters(fsig, to_sig) assert excinfo.value.args[0] == expected_exc.args[0] return pmap = Mapper.map_parameters(fsig, to_sig) assert pmap == {from_param.name: to_param.name}
def test_map_parameters_to_var_positional(self, from_kind): """ Ensure the mapping **strategy** produced mapping *to* ``var-positional`` - POSITIONAL_ONLY -> VAR_POSITIONAL (raises) - POSTIIONAL_OR_KEYWORD -> VAR_POSITIONAL (raises) - VAR_POSTIIONAL -> VAR_POSITIONAL (success) - KEYWORD_ONLY -> VAR_POSITIONAL (raises) - VAR_KEYWROD -> VAR_POSITIONAL (raises) """ from_param = self.make_param('from_', from_kind) from_sig = inspect.Signature([from_param]) fsig = FSignature.from_native(from_sig) to_param = self.make_param('args', VAR_POSITIONAL) to_sig = inspect.Signature([to_param]) if from_param.kind is VAR_POSITIONAL: pmap = Mapper.map_parameters(fsig, to_sig) assert pmap == {from_param.name: to_param.name} return with pytest.raises(TypeError) as excinfo: Mapper.map_parameters(fsig, to_sig) if from_param.kind is VAR_KEYWORD: assert excinfo.value.args[0] == ( "Missing requisite mapping from variable keyword parameter " "'from_'") else: assert excinfo.value.args[0] == \ "Missing requisite mapping from parameters (from_)"
def revise(self, previous: FSignature) -> FSignature: """ Replaces a parameter that matches :paramref:`~forge.replace.selector`. No validation is performed on the updated :class:`~forge.FSignature`, allowing it to be used as an intermediate revision in the context of :class:`~forge.compose`. :param previous: the :class:`~forge.FSignature` to modify :returns: a modified instance of :class:`~forge.FSignature` """ try: match = next(findparam(previous, self.selector)) except StopIteration: raise ValueError("No parameter matched selector '{}'".format( self.selector)) # https://github.com/python/mypy/issues/5156 return previous.replace( # type: ignore parameters=[ self.parameter if param is match else param for param in previous ], __validate_parameters__=False, )
def test_revise_none(self): """ Ensure that ``compose`` without any revisions is the identity function """ fsig = FSignature() rev = compose() assert rev.revise(fsig) is fsig
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_revise(self): """ Ensure that the revise function is the identity function """ rev = Revision() in_ = FSignature() assert rev.revise(in_) is in_
def revise(self, previous: FSignature) -> FSignature: """ Revises one or more parameters that matches :paramref:`~forge.modify.selector`. No validation is performed on the updated :class:`~forge.FSignature`, allowing it to be used as an intermediate revision in the context of :class:`~forge.compose`. :param previous: the :class:`~forge.FSignature` to modify :returns: a modified instance of :class:`~forge.FSignature` """ matched = list(findparam(previous, self.selector)) if not matched: if self.raising: raise ValueError("No parameter matched selector '{}'".format( self.selector)) return previous if not self.multiple: del matched[1:] # https://github.com/python/mypy/issues/5156 return previous.replace( # type: ignore parameters=[ param.replace(**self.updates) if param in matched else param for param in previous ], __validate_parameters__=False, )
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_no_match_raises(self): """ Ensure that if selector doesn't find a match, an exception is rasied. """ rev = replace('i', forge.arg('a')) with pytest.raises(ValueError) as excinfo: rev.revise(FSignature()) assert excinfo.value.args[0] == \ "No parameter matched selector 'i'"
def test_map_parameters_from_hidden(self, to_kind): """ Ensure mapping **strategy** success when no fparam provided. """ fsig = FSignature() to_param = self.make_param('a', to_kind, default=1) to_sig = inspect.Signature([to_param]) assert Mapper.map_parameters(fsig, to_sig) == {}
def test__repr__(self): """ Ensure the mapper is pretty printable with ``FSignature`` and ``inspect.Signature`` """ fsig = FSignature([forge.pos('a', 'b')]) callable_ = lambda *, b: None mapper = Mapper(fsig, callable_) assert repr(mapper) == '<Mapper (a, /) => (*, b)>'
def test_revise_no_validation(self): """ Ensure no validation is performed on the revision """ rev = delete('x', raising=False) fsig = FSignature( [forge.arg('b'), forge.pos('a')], __validate_parameters__=False, ) assert rev.revise(fsig) is fsig
def test_revise_void_cls(self): """ Ensure that passing ``void`` as a ``default`` or ``type`` is passed through (distinguishing _void from void). """ in_ = FSignature([forge.arg('x')]) rev = modify('x', default=forge.void, type=forge.void) out_ = rev.revise(in_) assert out_.parameters['x'].default is forge.void assert out_.parameters['x'].type is forge.void
def test_revise_no_validation(self): """ Ensure no validation is performed on the revision """ rev = returns(int) fsig = FSignature( [forge.arg('b'), forge.pos('a')], __validate_parameters__=False, ) assert rev.revise(fsig).parameters == fsig.parameters
def revise(self, previous: FSignature) -> FSignature: """ Translocates (moves) the :paramref:`~forge.insert.parameter` into a new position in the signature. No validation is performed on the updated :class:`~forge.FSignature`, allowing it to be used as an intermediate revision in the context of :class:`~forge.compose`. :param previous: the :class:`~forge.FSignature` to modify :returns: a modified instance of :class:`~forge.FSignature` """ try: selected = next(findparam(previous, self.selector)) except StopIteration: raise ValueError("No parameter matched selector '{}'".format( self.selector)) if self.before: try: before = next(findparam(previous, self.before)) except StopIteration: raise ValueError("No parameter matched selector '{}'".format( self.before)) parameters = [] for param in previous: if param is before: parameters.append(selected) elif param is selected: continue parameters.append(param) elif self.after: try: after = next(findparam(previous, self.after)) except StopIteration: raise ValueError("No parameter matched selector '{}'".format( self.after)) parameters = [] for param in previous: if param is not selected: parameters.append(param) if param is after: parameters.append(selected) else: parameters = [param for param in previous if param is not selected] parameters.insert(self.index, selected) # https://github.com/python/mypy/issues/5156 return previous.replace( # type: ignore parameters=parameters, __validate_parameters__=False, )
def test_revise(self): """ Ensure that manage revision passes the input signature to the user supplied function and returns (the user-defined function's) value. """ fsig = fsignature(lambda a, b, c: None) reverse = Mock( side_effect=lambda prev: prev.replace(parameters=prev[::-1])) rev = manage(reverse) assert rev.revise(fsig) == \ FSignature([forge.arg('c'), forge.arg('b'), forge.arg('a')])
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_get_context(self, has_context): """ Ensure the mapper retrieves the context value from arguments """ param = forge.ctx('param') \ if has_context \ else forge.arg('param') fsig = FSignature([param]) mapper = Mapper(fsig, lambda param: None) assert mapper.context_param == (param if has_context else None) kwargs = {'param': object()} ctx = mapper.get_context(kwargs) assert ctx == (kwargs['param'] if has_context else None)
def test_revise(self): """ Ensure that ``compose`` applies the underlying revisions from top to bottom. """ fsig1 = FSignature() mock1 = Mock( spec=Revision, revise=Mock(side_effect=lambda prev: fsig1), ) fsig2 = FSignature() mock2 = Mock( spec=Revision, revise=Mock(side_effect=lambda prev: fsig2), ) rev = compose(mock1, mock2) in_ = FSignature([forge.arg('a')]) assert rev.revise(in_) is fsig2 mock1.revise.assert_called_once_with(in_) mock2.revise.assert_called_once_with(fsig1)
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