def _test(self, outer, inner, args, kwargs, expected, expected_get, exp_src, exp_src_get): outer_f = support.f(outer, name='outer') inner_f = support.f(inner, name='inner') forw = specifiers.forwards_to_function(inner_f, *args, **kwargs)(outer_f) sig = specifiers.signature(forw) self.assertSigsEqual(sig, support.s(expected)) self.assertSourcesEqual( sig.sources, { 'outer': exp_src[0], 'inner': exp_src[1], '+depths': ['outer', 'inner'] }) sig_get = specifiers.signature(_util.safe_get(forw, object(), object)) self.assertSigsEqual(sig_get, support.s(expected_get)) self.assertSourcesEqual( sig_get.sources, { 'outer': exp_src_get[0], 'inner': exp_src_get[1], '+depths': ['outer', 'inner'] })
def forwards_tests(self, outer, inner, args, kwargs, expected, expected_get): outer_f = support.f(outer) inner_f = support.f(inner) forw = specifiers.forwards_to_function(inner_f, *args, **kwargs)(outer_f) self.assertSigsEqual(specifiers.signature(forw), support.s(expected)) self.assertSigsEqual( specifiers.signature(_util.safe_get(forw, object(), object)), support.s(expected_get))
def test_not_enough_pos_collect(self): @modifiers.annotate(args=parser.Parameter.REQUIRED) def func(*args): raise NotImplementedError csig = parser.CliSignature.from_signature(specifiers.signature(func)) self.assertRaises(errors.MissingRequiredArguments, read_arguments, csig, ())
def _test(self, func, expected, expected_src): sig = specifiers.signature(func) self.assertSigsEqual(sig, support.s(expected)) self.assertSourcesEqual(sig.sources, expected_src, func) with self.assertRaises(AssertionError): support.test_func_sig_coherent( func, check_return=False, check_invalid=False)
def _clize(fn, alias={}, force_positional=(), coerce={}, require_excess=False, extra=(), use_kwoargs=None): sig = specifiers.signature(fn) has_kwoargs = False annotations = defaultdict(list) ann_positional = [] for param in sig.parameters.values(): if param.kind == param.KEYWORD_ONLY: has_kwoargs = True elif require_excess and param.kind == param.VAR_POSITIONAL: annotations[param.name].append(parser.Parameter.REQUIRED) if param.annotation != param.empty: ann = util.maybe_iter(param.annotation) annotations[param.name].extend(ann) if clize.POSITIONAL in ann: ann_positional.append(param.name) annotations[param.name].remove(clize.POSITIONAL) for name, aliases in alias.items(): annotations[name].extend(aliases) for name, func in coerce.items(): annotations[name].append(func) fn = modifiers.annotate(**annotations)(fn) use_kwoargs = has_kwoargs if use_kwoargs is None else use_kwoargs if not use_kwoargs: fn = modifiers.autokwoargs( exceptions=chain(ann_positional, force_positional))(fn) return runner.Clize(fn, extra=extra)
def test_decorator_wraps(self): def decorator(function): @wraps(function) @modifiers.autokwoargs def _decorated(a, b=2, *args, **kwargs): function(1, *args, **kwargs) return _decorated func = decorator(_wrapped) sig = specifiers.signature(func) self.assertSigsEqual(sig, support.s('a, y, *, b=2, z')) self.assertEqual( sig.sources, { '+depths': { func: 0, _wrapped: 1 }, 'a': [func], 'b': [func], 'y': [_wrapped], 'z': [_wrapped] }) support.test_func_sig_coherent(func, check_return=False, check_invalid=False)
def test_directly_applied(self): def forger(obj): return support.s('abc') def forged(): raise NotImplementedError specifiers.set_signature_forger(forged, forger) self.assertSigsEqual(support.s('abc'), specifiers.signature(forged))
def test_forger_sig(self): @specifiers.forger_function def forger(p1, p2, p3, obj): raise NotImplementedError self.assertSigsEqual( support.s('p1, p2, p3, *, emulate=None'), specifiers.signature(forger))
def test_forger_sig(self): @specifiers.forger_function def forger(p1, p2, p3, obj): raise NotImplementedError self.assertSigsEqual(support.s('p1, p2, p3, *, emulate=None'), specifiers.signature(forger))
def _test(self, func, expected, sources, incoherent=False): sig = specifiers.signature(func) self.assertSigsEqual(sig, support.s(expected)) self.assertSourcesEqual(sig.sources, sources, func) if not incoherent: support.test_func_sig_coherent( func, check_return=False, check_invalid=False)
def _test(self, func, ensure_incoherent=True): self.assertSigsEqual( specifiers.signature(func), signatures.signature(func)) if ensure_incoherent: with self.assertRaises(AssertionError): support.test_func_sig_coherent( func, check_return=False, check_invalid=False)
def test_orig_sig(self): @specifiers.forger_function def forger(obj): return None @forger() def forged(alpha): raise NotImplementedError self.assertSigsEqual(support.s('alpha'), specifiers.signature(forged))
def test_deco(self): @specifiers.forger_function def forger(obj): return support.s('abc') @forger() def forged(): raise NotImplementedError self.assertSigsEqual(support.s('abc'), specifiers.signature(forged))
def test_not_enough_pos_collect(self): @modifiers.annotate(args=parser.Parameter.REQUIRED) def func(*args): raise NotImplementedError csig = parser.CliSignature.from_signature(specifiers.signature(func)) with self.assertRaises(errors.MissingRequiredArguments): self.read_arguments(csig, ())
def _test(self, func, ensure_incoherent=True): self.assertSigsEqual(specifiers.signature(func), signatures.signature(func)) if ensure_incoherent: with self.assertRaises(AssertionError): support.test_func_sig_coherent(func, check_return=False, check_invalid=False)
def _test(self, func, expected, sources, incoherent=False): sig = specifiers.signature(func) self.assertSigsEqual(sig, support.s(expected)) self.assertSourcesEqual(sig.sources, sources, func) if not incoherent: support.test_func_sig_coherent(func, check_return=False, check_invalid=False)
def test_wraps_other_preservation(self): def inner(a, b, c=1): raise NotImplementedError pok_inner = modifiers.autokwoargs(inner) def outer(x, y, z=2): raise NotImplementedError pok_outer = wraps(pok_inner)(modifiers.autokwoargs(outer)) self.assertEqual(pok_outer.func, outer) self.assertEqual(pok_outer.kwoarg_names, set('z')) self.assertSigsEqual(specifiers.signature(pok_outer), s('x, y, *, z=2'))
def test_fts(self): if sys.version_info >= (3,3): sup = super(self._Derivate, self._sub_inst).fts sig = specifiers.signature(self._sub_inst.fts) self.assertSigsEqual(sig, support.s('s, l, *, m')) self._test_raw_source(self._sub_inst.fts, 's, l, *, m', { '+depths': {self._sub_inst.fts: 0, sup: 1}, 's': [self._sub_inst.fts], 'l': [sup], 'm': [sup] })
def _test(self, outer, inner, args, kwargs, expected, expected_get, exp_src, exp_src_get): outer_f = support.f(outer, name='outer') inner_f = support.f(inner, name='inner') forw = specifiers.forwards_to_function(inner_f, *args, **kwargs)(outer_f) sig = specifiers.signature(forw) self.assertSigsEqual(sig, support.s(expected)) self.assertSourcesEqual(sig.sources, { 'outer': exp_src[0], 'inner': exp_src[1], '+depths': ['outer', 'inner']}) sig_get = specifiers.signature(_util.safe_get(forw, object(), object)) self.assertSigsEqual(sig_get, support.s(expected_get)) self.assertSourcesEqual(sig_get.sources, { 'outer': exp_src_get[0], 'inner': exp_src_get[1], '+depths': ['outer', 'inner']})
def test_missing_freevar(self): def make_closure(): var = 1 del var def func(a, *p, **k): var(*p, **k) # pyflakes: silence return func func = make_closure() self.assertSigsEqual( specifiers.signature(func), signatures.signature(func))
def test_dunder_call(self): sig = support.s('dunder, call') @specifiers.forger_function def forger(obj): return sig class MyClass(object): def __init__(self): forger()(self) def __call__(self): raise NotImplementedError self.assertSigsEqual(specifiers.signature(MyClass()), sig)
def test_specifiers_sig_after(self): inner = f('a, b', name='inner') outer = f('x, y, z, *args, **kwargs', name='outer') pt = modifiers.kwoargs('z')(outer) pt = specifiers.forwards_to_function(inner)(pt) sig = specifiers.signature(pt) self.assertSigsEqual(sig, s('x, y, a, b, *, z')) self.assertSourcesEqual( sig.sources, {'inner': 'ab', 'outer': 'xyz', '+depths': ['outer', 'inner']}) self.assertEqual(sig.sources['x'], [pt])
def test_as_forged_dunder_call_method(self): class MyClass(object): __signature__ = specifiers.as_forged @specifiers.forwards_to_method('method') def __call__(self, x, *args, **kwags): raise NotImplementedError def method(self, a, b, c): raise NotImplementedError exp = support.s('x, a, b, c') self.assertSigsEqual(signatures.signature(MyClass()), exp) self.assertSigsEqual(specifiers.signature(MyClass()), exp)
def test_no_sig(self): obj = object() sig = support.s('a, *p, **k') def sig_replace(obj_): if obj is obj_: raise ValueError("no sig for obj") else: return sig def func(a, *p, **k): obj(*p, **k) with patch.multiple(_util.funcsigs, signature=sig_replace): self.assertSigsEqual(specifiers.signature(func), sig)
def test_specifiers_sig_after(self): inner = f('a, b', name='inner') outer = f('x, y, z, *args, **kwargs', name='outer') pt = modifiers.kwoargs('z')(outer) pt = specifiers.forwards_to_function(inner)(pt) sig = specifiers.signature(pt) self.assertSigsEqual(sig, s('x, y, a, b, *, z')) self.assertSourcesEqual(sig.sources, { 'inner': 'ab', 'outer': 'xyz', '+depths': ['outer', 'inner'] }) self.assertEqual(sig.sources['x'], [pt])
def test_as_forged_dunder_call_method(self): class MyClass(object): __signature__ = specifiers.as_forged @specifiers.forwards_to_method('method') def __call__(self, x, *args, **kwags): raise NotImplementedError def method(self, a, b, c): raise NotImplementedError self.assertSigsEqual(specifiers.signature(MyClass()), support.s('x, a, b, c'))
def test_partial(self): def _wrapper(wrapped, a, *args, **kwargs): return wrapped(*args, **kwargs) func = partial(_wrapper, _wrapped) sig = specifiers.signature(func) self.assertSigsEqual(sig, support.s('a, x, y, *, z')) self.assertEqual(sig.sources, { 'a': [_wrapper], 'x': [_wrapped], 'y': [_wrapped], 'z': [_wrapped], '+depths': {func: 0, _wrapper: 1, _wrapped: 2} }) support.test_func_sig_coherent( func, check_return=False, check_invalid=False)
def test_functools_wrapped(self): @wraps(_wrapped) def func(a, *args, **kwargs): _wrapped(1, *args, **kwargs) sig = specifiers.signature(func) self.assertSigsEqual(sig, support.s('a, y, *, z')) self.assertEqual(sig.sources, { '+depths': {func: 0, _wrapped: 1}, 'a': [func], 'y': [_wrapped], 'z': [_wrapped] }) support.test_func_sig_coherent( func, check_return=False, check_invalid=False)
def __init__(self, decorator, **kwargs): super(DecoratedArgumentParameter, self).__init__(**kwargs) self.decorator = decorator self.cli = parser.CliSignature.from_signature( signatures.mask(specifiers.signature(decorator), 1)) self.extras = [ _redirect_ba(p, self) for p in self.cli.parameters.values() #if not isinstance(p, ForwarderParameter) ] try: super(DecoratedArgumentParameter, type(self)).required.__get__ except AttributeError: self._sub_required = self.required self.required = True
def __init__(self, decorator, **kwargs): super(DecoratedArgumentParameter, self).__init__(**kwargs) self.decorator = decorator self.cli = parser.CliSignature.from_signature(signatures.mask(specifiers.signature(decorator), 1)) self.extras = [ _redirect_ba(p, self) for p in self.cli.parameters.values() # if not isinstance(p, ForwarderParameter) ] try: super(DecoratedArgumentParameter, type(self)).required.__get__ except AttributeError: self._sub_required = self.required self.required = True
def test_fts(self): if sys.version_info >= (3, 3): sup = super(self._Derivate, self._sub_inst).fts sig = specifiers.signature(self._sub_inst.fts) self.assertSigsEqual(sig, support.s('s, l, *, m')) self._test_raw_source( self._sub_inst.fts, 's, l, *, m', { '+depths': { self._sub_inst.fts: 0, sup: 1 }, 's': [self._sub_inst.fts], 'l': [sup], 'm': [sup] })
def test_partial_kwo(self): """When given keyword arguments, functools.partial only makes them defaults. The full signature is therefore not fully determined, since the user can replace wrapped and change the meaning of *args, **kwargs. The substitution could be made in good faith that the user wouldn't change the value of the parameter, but this would potentially cause confusing documentation where a function description says remaining arguments will be forwarded to the given function, while the signature in the documentation only shows the default target's arguments. """ func = partial(AutoforwardsTests._wrapped_kwoarg, wrapped=_wrapped) expected = support.s('a, *args, wrapped=w, **kwargs', locals={'w': _wrapped}) self.assertSigsEqual(specifiers.signature(func), expected)
def test_forger_priority_over_autoforwards_hint(self): def make_func(): def real_inner(x, y, z): pass def inner(i, j): raise NotImplementedError @specifiers.forwards_to_function(inner) @modifiers.kwoargs('a') def outer(a, *args, **kwargs): real_inner(*args, **kwargs) return outer func = make_func() self.assertSigsEqual( support.s('i, j, *, a'), specifiers.signature(func)) func(1, 2, 3, a=4)
def s(*args, **kwargs): """Creates a signature from the given string representation of one. .. warning:: The contents of the arguments are eventually passed to `exec`. Do not use with untrusted input. :: >>> from sigtools.support import s >>> sig = s('a, b=2, *args, c:"annotation", **kwargs') >>> sig <inspect.Signature object at 0x7f15e6055550> >>> print(sig) (a, b=2, *args, c:'annotation', **kwargs) """ return specifiers.signature(f(*args, **kwargs))
def test_decorator_wraps(self): def decorator(function): @wraps(function) @modifiers.autokwoargs def _decorated(a, b=2, *args, **kwargs): function(1, *args, **kwargs) return _decorated func = decorator(_wrapped) sig = specifiers.signature(func) self.assertSigsEqual(sig, support.s('a, y, *, b=2, z')) self.assertEqual(sig.sources, { '+depths': {func: 0, _wrapped: 1}, 'a': [func], 'b': [func], 'y': [_wrapped], 'z': [_wrapped] }) support.test_func_sig_coherent( func, check_return=False, check_invalid=False)
def test_extend(self): def func1(arg, **kwargs): raise NotImplementedError def func2(arg, **kwargs): raise NotImplementedError def func3(arg, **kwargs): raise NotImplementedError def func4(arg, **kwargs): raise NotImplementedError c1 = Combination(func1, func2) c2 = Combination(func3, c1, func4) self.assertEqual(c2.functions, [func3, func1, func2, func4]) self.assertSigsEqual(s('arg, **kwargs'), signature(c2))
def test_emulation(self): func = specifiers.forwards_to_method('abc', emulate=False)(_func) self.assertTrue(_func is func) func = specifiers.forwards_to_method('abc')(_func) self.assertTrue(_func is func) class Cls(object): func = specifiers.forwards_to_method('abc')(_func) func = getattr(Cls.func, '__func__', func) self.assertTrue(_func is func) self.assertTrue(_func is Cls().func.__func__) class Cls(object): def func(self, abc, *args, **kwargs): raise NotImplementedError def abc(self, x): raise NotImplementedError method = Cls().func func = specifiers.forwards_to_method('abc')(method) self.assertTrue(isinstance(func, specifiers._ForgerWrapper)) self.assertEqual(func.__wrapped__, method) self.assertRaises(AttributeError, specifiers.forwards_to_method('abc', emulate=False), Cls().func) exp = support.s('abc, x') self.assertSigsEqual(signatures.signature(func), exp) self.assertSigsEqual(specifiers.signature(func), exp) class Emulator(object): def __init__(self, obj, forger): self.obj = obj self.forger = forger func = specifiers.forwards_to_function(func, emulate=Emulator)(_func) self.assertTrue(isinstance(func, Emulator)) @specifiers.forwards_to_function(_func, emulate=True) def func(x, y, *args, **kwargs): return x + y self.assertEqual(5, func(2, 3))
def _clize(fn, alias={}, force_positional=(), coerce={}, require_excess=False, extra=(), use_kwoargs=None): sig = specifiers.signature(fn) has_kwoargs = False annotations = defaultdict(list) ann_positional = [] for param in sig.parameters.values(): coerce_set = False if param.kind == param.KEYWORD_ONLY: has_kwoargs = True elif param.kind == param.VAR_KEYWORD: annotations[param.name].append(parser.Parameter.IGNORE) elif require_excess and param.kind == param.VAR_POSITIONAL: annotations[param.name].append(parser.Parameter.REQUIRED) if param.annotation != param.empty: for thing in util.maybe_iter(param.annotation): if thing == clize.POSITIONAL: ann_positional.append(param.name) continue elif callable(thing): coerce_set = True thing = _convert_coerce(thing) annotations[param.name].append(thing) try: func = coerce[param.name] except KeyError: pass else: annotations[param.name].append(_convert_coerce(func)) coerce_set = True annotations[param.name].extend(alias.get(param.name, ())) if not coerce_set and param.default != param.empty: annotations[param.name].append(_convert_coerce(type( param.default))) fn = modifiers.annotate(**annotations)(fn) use_kwoargs = has_kwoargs if use_kwoargs is None else use_kwoargs if not use_kwoargs: fn = modifiers.autokwoargs( exceptions=chain(ann_positional, force_positional))(fn) return runner.Clize(fn, extra=extra)
def test_forger_priority_over_autoforwards_hint(self): def make_func(): def real_inner(x, y, z): pass def inner(i, j): raise NotImplementedError @specifiers.forwards_to_function(inner) @modifiers.kwoargs('a') def outer(a, *args, **kwargs): real_inner(*args, **kwargs) return outer func = make_func() self.assertSigsEqual(support.s('i, j, *, a'), specifiers.signature(func)) func(1, 2, 3, a=4)
def test_functools_wrapped(self): @wraps(_wrapped) def func(a, *args, **kwargs): _wrapped(1, *args, **kwargs) sig = specifiers.signature(func) self.assertSigsEqual(sig, support.s('a, y, *, z')) self.assertEqual( sig.sources, { '+depths': { func: 0, _wrapped: 1 }, 'a': [func], 'y': [_wrapped], 'z': [_wrapped] }) support.test_func_sig_coherent(func, check_return=False, check_invalid=False)
def _clize(fn, alias={}, force_positional=(), coerce={}, require_excess=False, extra=(), use_kwoargs=None): sig = specifiers.signature(fn) has_kwoargs = False annotations = defaultdict(list) ann_positional = [] for param in sig.parameters.values(): coerce_set = False if param.kind == param.KEYWORD_ONLY: has_kwoargs = True elif param.kind == param.VAR_KEYWORD: annotations[param.name].append(parser.Parameter.IGNORE) elif require_excess and param.kind == param.VAR_POSITIONAL: annotations[param.name].append(parser.Parameter.REQUIRED) if param.annotation != param.empty: for thing in util.maybe_iter(param.annotation): if thing == clize.POSITIONAL: ann_positional.append(param.name) continue elif callable(thing): coerce_set = True thing = _convert_coerce(thing) annotations[param.name].append(thing) try: func = coerce[param.name] except KeyError: pass else: annotations[param.name].append(_convert_coerce(func)) coerce_set = True annotations[param.name].extend(alias.get(param.name, ())) if not coerce_set and param.default != param.empty: annotations[param.name].append( _convert_coerce(type(param.default))) fn = modifiers.annotate(**annotations)(fn) use_kwoargs = has_kwoargs if use_kwoargs is None else use_kwoargs if not use_kwoargs: fn = modifiers.autokwoargs( exceptions=chain(ann_positional, force_positional))(fn) return runner.Clize(fn, extra=extra)
def test_emulation(self): func = specifiers.forwards_to_method('abc', emulate=False)(_func) self.assertTrue(_func is func) func = specifiers.forwards_to_method('abc')(_func) self.assertTrue(_func is func) class Cls(object): func = specifiers.forwards_to_method('abc')(_func) func = getattr(Cls.func, '__func__', func) self.assertTrue(_func is func) self.assertTrue(_func is Cls().func.__func__) class Cls(object): def func(self, abc, *args, **kwargs): raise NotImplementedError def abc(self, x): raise NotImplementedError method = Cls().func func = specifiers.forwards_to_method('abc')(method) self.assertTrue(isinstance(func, specifiers._ForgerWrapper)) self.assertEqual(func.__wrapped__, method) self.assertRaises( AttributeError, specifiers.forwards_to_method('abc', emulate=False), Cls().func) exp = support.s('abc, x') self.assertSigsEqual(signatures.signature(func), exp) self.assertSigsEqual(specifiers.signature(func), exp) class Emulator(object): def __init__(self, obj, forger): self.obj = obj self.forger = forger func = specifiers.forwards_to_function(func, emulate=Emulator)(_func) self.assertTrue(isinstance(func, Emulator)) @specifiers.forwards_to_function(_func, emulate=True) def func(x, y, *args, **kwargs): return x + y self.assertEqual(5, func(2, 3))
def process_signature(app, what, name, obj, options, sig, return_annotation): try: parent, obj = fetch_dotted_name(name) except AttributeError: return sig, return_annotation if isinstance(obj, instancemethod): # python 2 unbound methods obj = obj.__func__ if isinstance(parent, type) and callable(obj): obj = _util.safe_get(obj, object(), type(parent)) try: sig = specifiers.signature(obj) except (TypeError, ValueError): # inspect.signature raises ValueError if obj is callable but it can't # determine a signature, eg. built-in objects return sig, return_annotation ret_annot = sig.return_annotation if ret_annot != sig.empty: sret_annot = '-> {0!r}'.format(ret_annot) sig = sig.replace(return_annotation=sig.empty) else: sret_annot = '' return str(sig), sret_annot
def test_partial(self): def _wrapper(wrapped, a, *args, **kwargs): return wrapped(*args, **kwargs) func = partial(_wrapper, _wrapped) sig = specifiers.signature(func) self.assertSigsEqual(sig, support.s('a, x, y, *, z')) self.assertEqual( sig.sources, { 'a': [_wrapper], 'x': [_wrapped], 'y': [_wrapped], 'z': [_wrapped], '+depths': { func: 0, _wrapper: 1, _wrapped: 2 } }) support.test_func_sig_coherent(func, check_return=False, check_invalid=False)
def test_func_sig_coherent(func, check_return=True, check_invalid=True): """Tests if a function is coherent with its signature. :param bool check_return: Check if the return value is correct (see `sort_callsigs`) :param bool check_invalid: Make sure call signatures invalid for the signature are also invalid for the passed callable. :raises: AssertionError """ sig = specifiers.signature(func) valid, invalid = sort_callsigs(sig, make_up_callsigs(sig, extra=2)) for args, kwargs, expected_ret in valid: try: ret = func(*args, **kwargs) except TypeError: raise AssertionError( '{0}{1} <- *{2}, **{3} raised TypeError' .format(_util.qualname(func), sig, args, kwargs)) else: if check_return and expected_ret != ret: raise AssertionError( '{0}{1} <- *{2}, **{3} returned {4} instead of {5}' .format(_util.qualname(func), sig, args, kwargs, ret, expected_ret)) if check_invalid: for args, kwargs in invalid: try: func(*args, **kwargs) except TypeError: pass else: raise AssertionError( '{0}{1} <- *{2}, **{3} did not raise TypeError as expected' .format(_util.qualname(func), sig, args, kwargs))
def sig_equal(self, obj, sig_str): self.assertSigsEqual(specifiers.signature(obj), support.s(sig_str), conv_first_posarg=True)
def func_signature(self): return signature(self.func)
def get_signature(self, obj): return signatures.merge( signatures.signature(self), *(specifiers.signature(func) for func in self.functions) )