mirror of
https://github.com/thegeeklab/ansible-later.git
synced 2024-11-16 10:00:39 +00:00
649 lines
16 KiB
Python
649 lines
16 KiB
Python
import platform
|
|
|
|
from toolz.functoolz import (thread_first, thread_last, memoize, curry,
|
|
compose, pipe, complement, do, juxt, flip, excepts)
|
|
from operator import add, mul, itemgetter
|
|
from toolz.utils import raises
|
|
from functools import partial
|
|
|
|
|
|
def iseven(x):
|
|
return x % 2 == 0
|
|
|
|
|
|
def isodd(x):
|
|
return x % 2 == 1
|
|
|
|
|
|
def inc(x):
|
|
return x + 1
|
|
|
|
|
|
def double(x):
|
|
return 2 * x
|
|
|
|
|
|
def test_thread_first():
|
|
assert thread_first(2) == 2
|
|
assert thread_first(2, inc) == 3
|
|
assert thread_first(2, inc, inc) == 4
|
|
assert thread_first(2, double, inc) == 5
|
|
assert thread_first(2, (add, 5), double) == 14
|
|
|
|
|
|
def test_thread_last():
|
|
assert list(thread_last([1, 2, 3], (map, inc), (filter, iseven))) == [2, 4]
|
|
assert list(thread_last([1, 2, 3], (map, inc), (filter, isodd))) == [3]
|
|
assert thread_last(2, (add, 5), double) == 14
|
|
|
|
|
|
def test_memoize():
|
|
fn_calls = [0] # Storage for side effects
|
|
|
|
def f(x, y):
|
|
""" A docstring """
|
|
fn_calls[0] += 1
|
|
return x + y
|
|
mf = memoize(f)
|
|
|
|
assert mf(2, 3) is mf(2, 3)
|
|
assert fn_calls == [1] # function was only called once
|
|
assert mf.__doc__ == f.__doc__
|
|
assert raises(TypeError, lambda: mf(1, {}))
|
|
|
|
|
|
def test_memoize_kwargs():
|
|
fn_calls = [0] # Storage for side effects
|
|
|
|
def f(x, y=0):
|
|
return x + y
|
|
|
|
mf = memoize(f)
|
|
|
|
assert mf(1) == f(1)
|
|
assert mf(1, 2) == f(1, 2)
|
|
assert mf(1, y=2) == f(1, y=2)
|
|
assert mf(1, y=3) == f(1, y=3)
|
|
|
|
|
|
def test_memoize_curried():
|
|
@curry
|
|
def f(x, y=0):
|
|
return x + y
|
|
|
|
f2 = f(y=1)
|
|
fm2 = memoize(f2)
|
|
|
|
assert fm2(3) == f2(3)
|
|
assert fm2(3) == f2(3)
|
|
|
|
|
|
def test_memoize_partial():
|
|
def f(x, y=0):
|
|
return x + y
|
|
|
|
f2 = partial(f, y=1)
|
|
fm2 = memoize(f2)
|
|
|
|
assert fm2(3) == f2(3)
|
|
assert fm2(3) == f2(3)
|
|
|
|
|
|
def test_memoize_key_signature():
|
|
# Single argument should not be tupled as a key. No keywords.
|
|
mf = memoize(lambda x: False, cache={1: True})
|
|
assert mf(1) is True
|
|
assert mf(2) is False
|
|
|
|
# Single argument must be tupled if signature has varargs. No keywords.
|
|
mf = memoize(lambda x, *args: False, cache={(1,): True, (1, 2): 2})
|
|
assert mf(1) is True
|
|
assert mf(2) is False
|
|
assert mf(1, 1) is False
|
|
assert mf(1, 2) == 2
|
|
assert mf((1, 2)) is False
|
|
|
|
# More than one argument is always tupled. No keywords.
|
|
mf = memoize(lambda x, y: False, cache={(1, 2): True})
|
|
assert mf(1, 2) is True
|
|
assert mf(1, 3) is False
|
|
assert raises(TypeError, lambda: mf((1, 2)))
|
|
|
|
# Nullary function (no inputs) uses empty tuple as the key
|
|
mf = memoize(lambda: False, cache={(): True})
|
|
assert mf() is True
|
|
|
|
# Single argument must be tupled if there are keyword arguments, because
|
|
# keyword arguments may be passed as unnamed args.
|
|
mf = memoize(lambda x, y=0: False,
|
|
cache={((1,), frozenset((('y', 2),))): 2,
|
|
((1, 2), None): 3})
|
|
assert mf(1, y=2) == 2
|
|
assert mf(1, 2) == 3
|
|
assert mf(2, y=2) is False
|
|
assert mf(2, 2) is False
|
|
assert mf(1) is False
|
|
assert mf((1, 2)) is False
|
|
|
|
# Keyword-only signatures must still have an "args" tuple.
|
|
mf = memoize(lambda x=0: False, cache={(None, frozenset((('x', 1),))): 1,
|
|
((1,), None): 2})
|
|
assert mf() is False
|
|
assert mf(x=1) == 1
|
|
assert mf(1) == 2
|
|
|
|
|
|
def test_memoize_curry_cache():
|
|
@memoize(cache={1: True})
|
|
def f(x):
|
|
return False
|
|
|
|
assert f(1) is True
|
|
assert f(2) is False
|
|
|
|
|
|
def test_memoize_key():
|
|
@memoize(key=lambda args, kwargs: args[0])
|
|
def f(x, y, *args, **kwargs):
|
|
return x + y
|
|
|
|
assert f(1, 2) == 3
|
|
assert f(1, 3) == 3
|
|
|
|
|
|
def test_memoize_wrapped():
|
|
|
|
def foo():
|
|
"""
|
|
Docstring
|
|
"""
|
|
pass
|
|
memoized_foo = memoize(foo)
|
|
assert memoized_foo.__wrapped__ is foo
|
|
|
|
|
|
def test_curry_simple():
|
|
cmul = curry(mul)
|
|
double = cmul(2)
|
|
assert callable(double)
|
|
assert double(10) == 20
|
|
assert repr(cmul) == repr(mul)
|
|
|
|
cmap = curry(map)
|
|
assert list(cmap(inc)([1, 2, 3])) == [2, 3, 4]
|
|
|
|
assert raises(TypeError, lambda: curry())
|
|
assert raises(TypeError, lambda: curry({1: 2}))
|
|
|
|
|
|
def test_curry_kwargs():
|
|
def f(a, b, c=10):
|
|
return (a + b) * c
|
|
|
|
f = curry(f)
|
|
assert f(1, 2, 3) == 9
|
|
assert f(1)(2, 3) == 9
|
|
assert f(1, 2) == 30
|
|
assert f(1, c=3)(2) == 9
|
|
assert f(c=3)(1, 2) == 9
|
|
|
|
def g(a=1, b=10, c=0):
|
|
return a + b + c
|
|
|
|
cg = curry(g, b=2)
|
|
assert cg() == 3
|
|
assert cg(b=3) == 4
|
|
assert cg(a=0) == 2
|
|
assert cg(a=0, b=1) == 1
|
|
assert cg(0) == 2 # pass "a" as arg, not kwarg
|
|
assert raises(TypeError, lambda: cg(1, 2)) # pass "b" as arg AND kwarg
|
|
|
|
def h(x, func=int):
|
|
return func(x)
|
|
|
|
if platform.python_implementation() != 'PyPy'\
|
|
or platform.python_version_tuple()[0] != '3': # Bug on PyPy3<2.5
|
|
# __init__ must not pick func as positional arg
|
|
assert curry(h)(0.0) == 0
|
|
assert curry(h)(func=str)(0.0) == '0.0'
|
|
assert curry(h, func=str)(0.0) == '0.0'
|
|
|
|
|
|
def test_curry_passes_errors():
|
|
@curry
|
|
def f(a, b):
|
|
if not isinstance(a, int):
|
|
raise TypeError()
|
|
return a + b
|
|
|
|
assert f(1, 2) == 3
|
|
assert raises(TypeError, lambda: f('1', 2))
|
|
assert raises(TypeError, lambda: f('1')(2))
|
|
assert raises(TypeError, lambda: f(1, 2, 3))
|
|
|
|
|
|
def test_curry_docstring():
|
|
def f(x, y):
|
|
""" A docstring """
|
|
return x
|
|
|
|
g = curry(f)
|
|
assert g.__doc__ == f.__doc__
|
|
assert str(g) == str(f)
|
|
assert f(1, 2) == g(1, 2)
|
|
|
|
|
|
def test_curry_is_like_partial():
|
|
def foo(a, b, c=1):
|
|
return a + b + c
|
|
|
|
p, c = partial(foo, 1, c=2), curry(foo)(1, c=2)
|
|
assert p.keywords == c.keywords
|
|
assert p.args == c.args
|
|
assert p(3) == c(3)
|
|
|
|
p, c = partial(foo, 1), curry(foo)(1)
|
|
assert p.keywords == c.keywords
|
|
assert p.args == c.args
|
|
assert p(3) == c(3)
|
|
assert p(3, c=2) == c(3, c=2)
|
|
|
|
p, c = partial(foo, c=1), curry(foo)(c=1)
|
|
assert p.keywords == c.keywords
|
|
assert p.args == c.args
|
|
assert p(1, 2) == c(1, 2)
|
|
|
|
|
|
def test_curry_is_idempotent():
|
|
def foo(a, b, c=1):
|
|
return a + b + c
|
|
|
|
f = curry(foo, 1, c=2)
|
|
g = curry(f)
|
|
assert isinstance(f, curry)
|
|
assert isinstance(g, curry)
|
|
assert not isinstance(g.func, curry)
|
|
assert not hasattr(g.func, 'func')
|
|
assert f.func == g.func
|
|
assert f.args == g.args
|
|
assert f.keywords == g.keywords
|
|
|
|
|
|
def test_curry_attributes_readonly():
|
|
def foo(a, b, c=1):
|
|
return a + b + c
|
|
|
|
f = curry(foo, 1, c=2)
|
|
assert raises(AttributeError, lambda: setattr(f, 'args', (2,)))
|
|
assert raises(AttributeError, lambda: setattr(f, 'keywords', {'c': 3}))
|
|
assert raises(AttributeError, lambda: setattr(f, 'func', f))
|
|
assert raises(AttributeError, lambda: delattr(f, 'args'))
|
|
assert raises(AttributeError, lambda: delattr(f, 'keywords'))
|
|
assert raises(AttributeError, lambda: delattr(f, 'func'))
|
|
|
|
|
|
def test_curry_attributes_writable():
|
|
def foo(a, b, c=1):
|
|
return a + b + c
|
|
foo.__qualname__ = 'this.is.foo'
|
|
f = curry(foo, 1, c=2)
|
|
assert f.__qualname__ == 'this.is.foo'
|
|
f.__name__ = 'newname'
|
|
f.__doc__ = 'newdoc'
|
|
f.__module__ = 'newmodule'
|
|
f.__qualname__ = 'newqualname'
|
|
assert f.__name__ == 'newname'
|
|
assert f.__doc__ == 'newdoc'
|
|
assert f.__module__ == 'newmodule'
|
|
assert f.__qualname__ == 'newqualname'
|
|
if hasattr(f, 'func_name'):
|
|
assert f.__name__ == f.func_name
|
|
|
|
|
|
def test_curry_module():
|
|
from toolz.curried.exceptions import merge
|
|
assert merge.__module__ == 'toolz.curried.exceptions'
|
|
|
|
|
|
def test_curry_comparable():
|
|
def foo(a, b, c=1):
|
|
return a + b + c
|
|
f1 = curry(foo, 1, c=2)
|
|
f2 = curry(foo, 1, c=2)
|
|
g1 = curry(foo, 1, c=3)
|
|
h1 = curry(foo, c=2)
|
|
h2 = h1(c=2)
|
|
h3 = h1()
|
|
assert f1 == f2
|
|
assert not (f1 != f2)
|
|
assert f1 != g1
|
|
assert not (f1 == g1)
|
|
assert f1 != h1
|
|
assert h1 == h2
|
|
assert h1 == h3
|
|
|
|
# test function comparison works
|
|
def bar(a, b, c=1):
|
|
return a + b + c
|
|
b1 = curry(bar, 1, c=2)
|
|
assert b1 != f1
|
|
|
|
assert set([f1, f2, g1, h1, h2, h3, b1, b1()]) == set([f1, g1, h1, b1])
|
|
|
|
# test unhashable input
|
|
unhash1 = curry(foo, [])
|
|
assert raises(TypeError, lambda: hash(unhash1))
|
|
unhash2 = curry(foo, c=[])
|
|
assert raises(TypeError, lambda: hash(unhash2))
|
|
|
|
|
|
def test_curry_doesnot_transmogrify():
|
|
# Early versions of `curry` transmogrified to `partial` objects if
|
|
# only one positional argument remained even if keyword arguments
|
|
# were present. Now, `curry` should always remain `curry`.
|
|
def f(x, y=0):
|
|
return x + y
|
|
|
|
cf = curry(f)
|
|
assert cf(y=1)(y=2)(y=3)(1) == f(1, 3)
|
|
|
|
|
|
def test_curry_on_classmethods():
|
|
class A(object):
|
|
BASE = 10
|
|
|
|
def __init__(self, base):
|
|
self.BASE = base
|
|
|
|
@curry
|
|
def addmethod(self, x, y):
|
|
return self.BASE + x + y
|
|
|
|
@classmethod
|
|
@curry
|
|
def addclass(cls, x, y):
|
|
return cls.BASE + x + y
|
|
|
|
@staticmethod
|
|
@curry
|
|
def addstatic(x, y):
|
|
return x + y
|
|
|
|
a = A(100)
|
|
assert a.addmethod(3, 4) == 107
|
|
assert a.addmethod(3)(4) == 107
|
|
assert A.addmethod(a, 3, 4) == 107
|
|
assert A.addmethod(a)(3)(4) == 107
|
|
|
|
assert a.addclass(3, 4) == 17
|
|
assert a.addclass(3)(4) == 17
|
|
assert A.addclass(3, 4) == 17
|
|
assert A.addclass(3)(4) == 17
|
|
|
|
assert a.addstatic(3, 4) == 7
|
|
assert a.addstatic(3)(4) == 7
|
|
assert A.addstatic(3, 4) == 7
|
|
assert A.addstatic(3)(4) == 7
|
|
|
|
# we want this to be of type curry
|
|
assert isinstance(a.addmethod, curry)
|
|
assert isinstance(A.addmethod, curry)
|
|
|
|
|
|
def test_memoize_on_classmethods():
|
|
class A(object):
|
|
BASE = 10
|
|
HASH = 10
|
|
|
|
def __init__(self, base):
|
|
self.BASE = base
|
|
|
|
@memoize
|
|
def addmethod(self, x, y):
|
|
return self.BASE + x + y
|
|
|
|
@classmethod
|
|
@memoize
|
|
def addclass(cls, x, y):
|
|
return cls.BASE + x + y
|
|
|
|
@staticmethod
|
|
@memoize
|
|
def addstatic(x, y):
|
|
return x + y
|
|
|
|
def __hash__(self):
|
|
return self.HASH
|
|
|
|
a = A(100)
|
|
assert a.addmethod(3, 4) == 107
|
|
assert A.addmethod(a, 3, 4) == 107
|
|
|
|
a.BASE = 200
|
|
assert a.addmethod(3, 4) == 107
|
|
a.HASH = 200
|
|
assert a.addmethod(3, 4) == 207
|
|
|
|
assert a.addclass(3, 4) == 17
|
|
assert A.addclass(3, 4) == 17
|
|
A.BASE = 20
|
|
assert A.addclass(3, 4) == 17
|
|
A.HASH = 20 # hashing of class is handled by metaclass
|
|
assert A.addclass(3, 4) == 17 # hence, != 27
|
|
|
|
assert a.addstatic(3, 4) == 7
|
|
assert A.addstatic(3, 4) == 7
|
|
|
|
|
|
def test_curry_call():
|
|
@curry
|
|
def add(x, y):
|
|
return x + y
|
|
assert raises(TypeError, lambda: add.call(1))
|
|
assert add(1)(2) == add.call(1, 2)
|
|
assert add(1)(2) == add(1).call(2)
|
|
|
|
|
|
def test_curry_bind():
|
|
@curry
|
|
def add(x=1, y=2):
|
|
return x + y
|
|
assert add() == add(1, 2)
|
|
assert add.bind(10)(20) == add(10, 20)
|
|
assert add.bind(10).bind(20)() == add(10, 20)
|
|
assert add.bind(x=10)(y=20) == add(10, 20)
|
|
assert add.bind(x=10).bind(y=20)() == add(10, 20)
|
|
|
|
|
|
def test_curry_unknown_args():
|
|
def add3(x, y, z):
|
|
return x + y + z
|
|
|
|
@curry
|
|
def f(*args):
|
|
return add3(*args)
|
|
|
|
assert f()(1)(2)(3) == 6
|
|
assert f(1)(2)(3) == 6
|
|
assert f(1, 2)(3) == 6
|
|
assert f(1, 2, 3) == 6
|
|
assert f(1, 2)(3, 4) == f(1, 2, 3, 4)
|
|
|
|
|
|
def test_curry_bad_types():
|
|
assert raises(TypeError, lambda: curry(1))
|
|
|
|
|
|
def test_curry_subclassable():
|
|
class mycurry(curry):
|
|
pass
|
|
|
|
add = mycurry(lambda x, y: x+y)
|
|
assert isinstance(add, curry)
|
|
assert isinstance(add, mycurry)
|
|
assert isinstance(add(1), mycurry)
|
|
assert isinstance(add()(1), mycurry)
|
|
assert add(1)(2) == 3
|
|
|
|
# Should we make `_should_curry` public?
|
|
"""
|
|
class curry2(curry):
|
|
def _should_curry(self, args, kwargs, exc=None):
|
|
return len(self.args) + len(args) < 2
|
|
|
|
add = curry2(lambda x, y: x+y)
|
|
assert isinstance(add(1), curry2)
|
|
assert add(1)(2) == 3
|
|
assert isinstance(add(1)(x=2), curry2)
|
|
assert raises(TypeError, lambda: add(1)(x=2)(3))
|
|
"""
|
|
|
|
|
|
def test_compose():
|
|
assert compose()(0) == 0
|
|
assert compose(inc)(0) == 1
|
|
assert compose(double, inc)(0) == 2
|
|
assert compose(str, iseven, inc, double)(3) == "False"
|
|
assert compose(str, add)(1, 2) == '3'
|
|
|
|
def f(a, b, c=10):
|
|
return (a + b) * c
|
|
|
|
assert compose(str, inc, f)(1, 2, c=3) == '10'
|
|
|
|
# Define two functions with different names
|
|
def f(a):
|
|
return a
|
|
|
|
def g(a):
|
|
return a
|
|
|
|
composed = compose(f, g)
|
|
assert composed.__name__ == 'f_of_g'
|
|
assert composed.__doc__ == 'lambda *args, **kwargs: f(g(*args, **kwargs))'
|
|
|
|
# Create an object with no __name__.
|
|
h = object()
|
|
|
|
composed = compose(f, h)
|
|
assert composed.__name__ == 'Compose'
|
|
assert composed.__doc__ == 'A composition of functions'
|
|
|
|
|
|
def test_pipe():
|
|
assert pipe(1, inc) == 2
|
|
assert pipe(1, inc, inc) == 3
|
|
assert pipe(1, double, inc, iseven) is False
|
|
|
|
|
|
def test_complement():
|
|
# No args:
|
|
assert complement(lambda: False)()
|
|
assert not complement(lambda: True)()
|
|
|
|
# Single arity:
|
|
assert complement(iseven)(1)
|
|
assert not complement(iseven)(2)
|
|
assert complement(complement(iseven))(2)
|
|
assert not complement(complement(isodd))(2)
|
|
|
|
# Multiple arities:
|
|
both_even = lambda a, b: iseven(a) and iseven(b)
|
|
assert complement(both_even)(1, 2)
|
|
assert not complement(both_even)(2, 2)
|
|
|
|
# Generic truthiness:
|
|
assert complement(lambda: "")()
|
|
assert complement(lambda: 0)()
|
|
assert complement(lambda: None)()
|
|
assert complement(lambda: [])()
|
|
|
|
assert not complement(lambda: "x")()
|
|
assert not complement(lambda: 1)()
|
|
assert not complement(lambda: [1])()
|
|
|
|
|
|
def test_do():
|
|
inc = lambda x: x + 1
|
|
assert do(inc, 1) == 1
|
|
|
|
log = []
|
|
assert do(log.append, 1) == 1
|
|
assert log == [1]
|
|
|
|
|
|
def test_juxt_generator_input():
|
|
data = list(range(10))
|
|
juxtfunc = juxt(itemgetter(2*i) for i in range(5))
|
|
assert juxtfunc(data) == (0, 2, 4, 6, 8)
|
|
assert juxtfunc(data) == (0, 2, 4, 6, 8)
|
|
|
|
|
|
def test_flip():
|
|
def f(a, b):
|
|
return a, b
|
|
|
|
assert flip(f, 'a', 'b') == ('b', 'a')
|
|
|
|
|
|
def test_excepts():
|
|
# These are descriptors, make sure this works correctly.
|
|
assert excepts.__name__ == 'excepts'
|
|
assert (
|
|
'A wrapper around a function to catch exceptions and\n'
|
|
' dispatch to a handler.\n'
|
|
) in excepts.__doc__
|
|
|
|
def idx(a):
|
|
"""idx docstring
|
|
"""
|
|
return [1, 2].index(a)
|
|
|
|
def handler(e):
|
|
"""handler docstring
|
|
"""
|
|
assert isinstance(e, ValueError)
|
|
return -1
|
|
|
|
excepting = excepts(ValueError, idx, handler)
|
|
assert excepting(1) == 0
|
|
assert excepting(2) == 1
|
|
assert excepting(3) == -1
|
|
|
|
assert excepting.__name__ == 'idx_excepting_ValueError'
|
|
assert 'idx docstring' in excepting.__doc__
|
|
assert 'ValueError' in excepting.__doc__
|
|
assert 'handler docstring' in excepting.__doc__
|
|
|
|
def getzero(a):
|
|
"""getzero docstring
|
|
"""
|
|
return a[0]
|
|
|
|
excepting = excepts((IndexError, KeyError), getzero)
|
|
assert excepting([]) is None
|
|
assert excepting([1]) == 1
|
|
assert excepting({}) is None
|
|
assert excepting({0: 1}) == 1
|
|
|
|
assert excepting.__name__ == 'getzero_excepting_IndexError_or_KeyError'
|
|
assert 'getzero docstring' in excepting.__doc__
|
|
assert 'return_none' in excepting.__doc__
|
|
assert 'Returns None' in excepting.__doc__
|
|
|
|
def raise_(a):
|
|
"""A function that raises an instance of the exception type given.
|
|
"""
|
|
raise a()
|
|
|
|
excepting = excepts((ValueError, KeyError), raise_)
|
|
assert excepting(ValueError) is None
|
|
assert excepting(KeyError) is None
|
|
assert raises(TypeError, lambda: excepting(TypeError))
|
|
assert raises(NotImplementedError, lambda: excepting(NotImplementedError))
|
|
|
|
excepting = excepts(object(), object(), object())
|
|
assert excepting.__name__ == 'excepting'
|
|
assert excepting.__doc__ == excepts.__doc__
|
|
|