test_functoolz.py 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811
  1. import inspect
  2. import sys
  3. import cytoolz
  4. from cytoolz.functoolz import (thread_first, thread_last, memoize, curry,
  5. compose, compose_left, pipe, complement, do, juxt,
  6. flip, excepts, apply)
  7. from operator import add, mul, itemgetter
  8. from cytoolz.utils import raises
  9. from functools import partial
  10. IS_PYPY_GE_39 = (
  11. sys.implementation.name == "pypy"
  12. and sys.version_info.major == 3
  13. and sys.version_info.minor >= 9
  14. )
  15. def iseven(x):
  16. return x % 2 == 0
  17. def isodd(x):
  18. return x % 2 == 1
  19. def inc(x):
  20. return x + 1
  21. def double(x):
  22. return 2 * x
  23. class AlwaysEquals(object):
  24. """useful to test correct __eq__ implementation of other objects"""
  25. def __eq__(self, other):
  26. return True
  27. def __ne__(self, other):
  28. return False
  29. class NeverEquals(object):
  30. """useful to test correct __eq__ implementation of other objects"""
  31. def __eq__(self, other):
  32. return False
  33. def __ne__(self, other):
  34. return True
  35. def test_apply():
  36. assert apply(double, 5) == 10
  37. assert tuple(map(apply, [double, inc, double], [10, 500, 8000])) == (20, 501, 16000)
  38. assert raises(TypeError, apply)
  39. def test_thread_first():
  40. assert thread_first(2) == 2
  41. assert thread_first(2, inc) == 3
  42. assert thread_first(2, inc, inc) == 4
  43. assert thread_first(2, double, inc) == 5
  44. assert thread_first(2, (add, 5), double) == 14
  45. def test_thread_last():
  46. assert list(thread_last([1, 2, 3], (map, inc), (filter, iseven))) == [2, 4]
  47. assert list(thread_last([1, 2, 3], (map, inc), (filter, isodd))) == [3]
  48. assert thread_last(2, (add, 5), double) == 14
  49. def test_memoize():
  50. fn_calls = [0] # Storage for side effects
  51. def f(x, y):
  52. """ A docstring """
  53. fn_calls[0] += 1
  54. return x + y
  55. mf = memoize(f)
  56. assert mf(2, 3) is mf(2, 3)
  57. assert fn_calls == [1] # function was only called once
  58. assert mf.__doc__ == f.__doc__
  59. assert raises(TypeError, lambda: mf(1, {}))
  60. def test_memoize_kwargs():
  61. fn_calls = [0] # Storage for side effects
  62. def f(x, y=0):
  63. return x + y
  64. mf = memoize(f)
  65. assert mf(1) == f(1)
  66. assert mf(1, 2) == f(1, 2)
  67. assert mf(1, y=2) == f(1, y=2)
  68. assert mf(1, y=3) == f(1, y=3)
  69. def test_memoize_curried():
  70. @curry
  71. def f(x, y=0):
  72. return x + y
  73. f2 = f(y=1)
  74. fm2 = memoize(f2)
  75. assert fm2(3) == f2(3)
  76. assert fm2(3) == f2(3)
  77. def test_memoize_partial():
  78. def f(x, y=0):
  79. return x + y
  80. f2 = partial(f, y=1)
  81. fm2 = memoize(f2)
  82. assert fm2(3) == f2(3)
  83. assert fm2(3) == f2(3)
  84. def test_memoize_key_signature():
  85. # Single argument should not be tupled as a key. No keywords.
  86. mf = memoize(lambda x: False, cache={1: True})
  87. assert mf(1) is True
  88. assert mf(2) is False
  89. # Single argument must be tupled if signature has varargs. No keywords.
  90. mf = memoize(lambda x, *args: False, cache={(1,): True, (1, 2): 2})
  91. assert mf(1) is True
  92. assert mf(2) is False
  93. assert mf(1, 1) is False
  94. assert mf(1, 2) == 2
  95. assert mf((1, 2)) is False
  96. # More than one argument is always tupled. No keywords.
  97. mf = memoize(lambda x, y: False, cache={(1, 2): True})
  98. assert mf(1, 2) is True
  99. assert mf(1, 3) is False
  100. assert raises(TypeError, lambda: mf((1, 2)))
  101. # Nullary function (no inputs) uses empty tuple as the key
  102. mf = memoize(lambda: False, cache={(): True})
  103. assert mf() is True
  104. # Single argument must be tupled if there are keyword arguments, because
  105. # keyword arguments may be passed as unnamed args.
  106. mf = memoize(lambda x, y=0: False,
  107. cache={((1,), frozenset((('y', 2),))): 2,
  108. ((1, 2), None): 3})
  109. assert mf(1, y=2) == 2
  110. assert mf(1, 2) == 3
  111. assert mf(2, y=2) is False
  112. assert mf(2, 2) is False
  113. assert mf(1) is False
  114. assert mf((1, 2)) is False
  115. # Keyword-only signatures must still have an "args" tuple.
  116. mf = memoize(lambda x=0: False, cache={(None, frozenset((('x', 1),))): 1,
  117. ((1,), None): 2})
  118. assert mf() is False
  119. assert mf(x=1) == 1
  120. assert mf(1) == 2
  121. def test_memoize_curry_cache():
  122. @memoize(cache={1: True})
  123. def f(x):
  124. return False
  125. assert f(1) is True
  126. assert f(2) is False
  127. def test_memoize_key():
  128. @memoize(key=lambda args, kwargs: args[0])
  129. def f(x, y, *args, **kwargs):
  130. return x + y
  131. assert f(1, 2) == 3
  132. assert f(1, 3) == 3
  133. def test_memoize_wrapped():
  134. def foo():
  135. """
  136. Docstring
  137. """
  138. pass
  139. memoized_foo = memoize(foo)
  140. assert memoized_foo.__wrapped__ is foo
  141. def test_curry_simple():
  142. cmul = curry(mul)
  143. double = cmul(2)
  144. assert callable(double)
  145. assert double(10) == 20
  146. assert repr(cmul) == repr(mul)
  147. cmap = curry(map)
  148. assert list(cmap(inc)([1, 2, 3])) == [2, 3, 4]
  149. assert raises(TypeError, lambda: curry())
  150. assert raises(TypeError, lambda: curry({1: 2}))
  151. def test_curry_kwargs():
  152. def f(a, b, c=10):
  153. return (a + b) * c
  154. f = curry(f)
  155. assert f(1, 2, 3) == 9
  156. assert f(1)(2, 3) == 9
  157. assert f(1, 2) == 30
  158. assert f(1, c=3)(2) == 9
  159. assert f(c=3)(1, 2) == 9
  160. def g(a=1, b=10, c=0):
  161. return a + b + c
  162. cg = curry(g, b=2)
  163. assert cg() == 3
  164. assert cg(b=3) == 4
  165. assert cg(a=0) == 2
  166. assert cg(a=0, b=1) == 1
  167. assert cg(0) == 2 # pass "a" as arg, not kwarg
  168. assert raises(TypeError, lambda: cg(1, 2)) # pass "b" as arg AND kwarg
  169. def h(x, func=int):
  170. return func(x)
  171. # __init__ must not pick func as positional arg
  172. assert curry(h)(0.0) == 0
  173. assert curry(h)(func=str)(0.0) == '0.0'
  174. assert curry(h, func=str)(0.0) == '0.0'
  175. def test_curry_passes_errors():
  176. @curry
  177. def f(a, b):
  178. if not isinstance(a, int):
  179. raise TypeError()
  180. return a + b
  181. assert f(1, 2) == 3
  182. assert raises(TypeError, lambda: f('1', 2))
  183. assert raises(TypeError, lambda: f('1')(2))
  184. assert raises(TypeError, lambda: f(1, 2, 3))
  185. def test_curry_docstring():
  186. def f(x, y):
  187. """ A docstring """
  188. return x
  189. g = curry(f)
  190. if not IS_PYPY_GE_39: # pypy >=3.9 doesn't like __doc__ property
  191. assert g.__doc__ == f.__doc__
  192. assert str(g) == str(f)
  193. assert f(1, 2) == g(1, 2)
  194. def test_curry_is_like_partial():
  195. def foo(a, b, c=1):
  196. return a + b + c
  197. p, c = partial(foo, 1, c=2), curry(foo)(1, c=2)
  198. assert p.keywords == c.keywords
  199. assert p.args == c.args
  200. assert p(3) == c(3)
  201. p, c = partial(foo, 1), curry(foo)(1)
  202. assert p.keywords == c.keywords
  203. assert p.args == c.args
  204. assert p(3) == c(3)
  205. assert p(3, c=2) == c(3, c=2)
  206. p, c = partial(foo, c=1), curry(foo)(c=1)
  207. assert p.keywords == c.keywords
  208. assert p.args == c.args
  209. assert p(1, 2) == c(1, 2)
  210. def test_curry_is_idempotent():
  211. def foo(a, b, c=1):
  212. return a + b + c
  213. f = curry(foo, 1, c=2)
  214. g = curry(f)
  215. assert isinstance(f, curry)
  216. assert isinstance(g, curry)
  217. assert not isinstance(g.func, curry)
  218. assert not hasattr(g.func, 'func')
  219. assert f.func == g.func
  220. assert f.args == g.args
  221. assert f.keywords == g.keywords
  222. def test_curry_attributes_readonly():
  223. def foo(a, b, c=1):
  224. return a + b + c
  225. f = curry(foo, 1, c=2)
  226. assert raises(AttributeError, lambda: setattr(f, 'args', (2,)))
  227. assert raises(AttributeError, lambda: setattr(f, 'keywords', {'c': 3}))
  228. assert raises(AttributeError, lambda: setattr(f, 'func', f))
  229. assert raises(AttributeError, lambda: delattr(f, 'args'))
  230. assert raises(AttributeError, lambda: delattr(f, 'keywords'))
  231. assert raises(AttributeError, lambda: delattr(f, 'func'))
  232. def test_curry_attributes_writable():
  233. def foo(a, b, c=1):
  234. return a + b + c
  235. foo.__qualname__ = 'this.is.foo'
  236. f = curry(foo, 1, c=2)
  237. assert f.__qualname__ == 'this.is.foo'
  238. f.__name__ = 'newname'
  239. f.__doc__ = 'newdoc'
  240. f.__module__ = 'newmodule'
  241. f.__qualname__ = 'newqualname'
  242. assert f.__name__ == 'newname'
  243. assert f.__doc__ == 'newdoc'
  244. assert f.__module__ == 'newmodule'
  245. assert f.__qualname__ == 'newqualname'
  246. if hasattr(f, 'func_name'):
  247. assert f.__name__ == f.func_name
  248. def test_curry_module():
  249. from cytoolz.curried.exceptions import merge
  250. assert merge.__module__ == 'cytoolz.curried.exceptions'
  251. def test_curry_comparable():
  252. def foo(a, b, c=1):
  253. return a + b + c
  254. f1 = curry(foo, 1, c=2)
  255. f2 = curry(foo, 1, c=2)
  256. g1 = curry(foo, 1, c=3)
  257. h1 = curry(foo, c=2)
  258. h2 = h1(c=2)
  259. h3 = h1()
  260. assert f1 == f2
  261. assert not (f1 != f2)
  262. assert f1 != g1
  263. assert not (f1 == g1)
  264. assert f1 != h1
  265. assert h1 == h2
  266. assert h1 == h3
  267. # test function comparison works
  268. def bar(a, b, c=1):
  269. return a + b + c
  270. b1 = curry(bar, 1, c=2)
  271. assert b1 != f1
  272. assert {f1, f2, g1, h1, h2, h3, b1, b1()} == {f1, g1, h1, b1}
  273. # test unhashable input
  274. unhash1 = curry(foo, [])
  275. assert raises(TypeError, lambda: hash(unhash1))
  276. unhash2 = curry(foo, c=[])
  277. assert raises(TypeError, lambda: hash(unhash2))
  278. def test_curry_doesnot_transmogrify():
  279. # Early versions of `curry` transmogrified to `partial` objects if
  280. # only one positional argument remained even if keyword arguments
  281. # were present. Now, `curry` should always remain `curry`.
  282. def f(x, y=0):
  283. return x + y
  284. cf = curry(f)
  285. assert cf(y=1)(y=2)(y=3)(1) == f(1, 3)
  286. def test_curry_on_classmethods():
  287. class A(object):
  288. BASE = 10
  289. def __init__(self, base):
  290. self.BASE = base
  291. @curry
  292. def addmethod(self, x, y):
  293. return self.BASE + x + y
  294. @classmethod
  295. @curry
  296. def addclass(cls, x, y):
  297. return cls.BASE + x + y
  298. @staticmethod
  299. @curry
  300. def addstatic(x, y):
  301. return x + y
  302. a = A(100)
  303. assert a.addmethod(3, 4) == 107
  304. assert a.addmethod(3)(4) == 107
  305. assert A.addmethod(a, 3, 4) == 107
  306. assert A.addmethod(a)(3)(4) == 107
  307. assert a.addclass(3, 4) == 17
  308. assert a.addclass(3)(4) == 17
  309. assert A.addclass(3, 4) == 17
  310. assert A.addclass(3)(4) == 17
  311. assert a.addstatic(3, 4) == 7
  312. assert a.addstatic(3)(4) == 7
  313. assert A.addstatic(3, 4) == 7
  314. assert A.addstatic(3)(4) == 7
  315. # we want this to be of type curry
  316. assert isinstance(a.addmethod, curry)
  317. assert isinstance(A.addmethod, curry)
  318. def test_memoize_on_classmethods():
  319. class A(object):
  320. BASE = 10
  321. HASH = 10
  322. def __init__(self, base):
  323. self.BASE = base
  324. @memoize
  325. def addmethod(self, x, y):
  326. return self.BASE + x + y
  327. @classmethod
  328. @memoize
  329. def addclass(cls, x, y):
  330. return cls.BASE + x + y
  331. @staticmethod
  332. @memoize
  333. def addstatic(x, y):
  334. return x + y
  335. def __hash__(self):
  336. return self.HASH
  337. a = A(100)
  338. assert a.addmethod(3, 4) == 107
  339. assert A.addmethod(a, 3, 4) == 107
  340. a.BASE = 200
  341. assert a.addmethod(3, 4) == 107
  342. a.HASH = 200
  343. assert a.addmethod(3, 4) == 207
  344. assert a.addclass(3, 4) == 17
  345. assert A.addclass(3, 4) == 17
  346. A.BASE = 20
  347. assert A.addclass(3, 4) == 17
  348. A.HASH = 20 # hashing of class is handled by metaclass
  349. assert A.addclass(3, 4) == 17 # hence, != 27
  350. assert a.addstatic(3, 4) == 7
  351. assert A.addstatic(3, 4) == 7
  352. def test_curry_call():
  353. @curry
  354. def add(x, y):
  355. return x + y
  356. assert raises(TypeError, lambda: add.call(1))
  357. assert add(1)(2) == add.call(1, 2)
  358. assert add(1)(2) == add(1).call(2)
  359. def test_curry_bind():
  360. @curry
  361. def add(x=1, y=2):
  362. return x + y
  363. assert add() == add(1, 2)
  364. assert add.bind(10)(20) == add(10, 20)
  365. assert add.bind(10).bind(20)() == add(10, 20)
  366. assert add.bind(x=10)(y=20) == add(10, 20)
  367. assert add.bind(x=10).bind(y=20)() == add(10, 20)
  368. def test_curry_unknown_args():
  369. def add3(x, y, z):
  370. return x + y + z
  371. @curry
  372. def f(*args):
  373. return add3(*args)
  374. assert f()(1)(2)(3) == 6
  375. assert f(1)(2)(3) == 6
  376. assert f(1, 2)(3) == 6
  377. assert f(1, 2, 3) == 6
  378. assert f(1, 2)(3, 4) == f(1, 2, 3, 4)
  379. def test_curry_bad_types():
  380. assert raises(TypeError, lambda: curry(1))
  381. def test_curry_subclassable():
  382. class mycurry(curry):
  383. pass
  384. add = mycurry(lambda x, y: x+y)
  385. assert isinstance(add, curry)
  386. assert isinstance(add, mycurry)
  387. assert isinstance(add(1), mycurry)
  388. assert isinstance(add()(1), mycurry)
  389. assert add(1)(2) == 3
  390. # Should we make `_should_curry` public?
  391. """
  392. class curry2(curry):
  393. def _should_curry(self, args, kwargs, exc=None):
  394. return len(self.args) + len(args) < 2
  395. add = curry2(lambda x, y: x+y)
  396. assert isinstance(add(1), curry2)
  397. assert add(1)(2) == 3
  398. assert isinstance(add(1)(x=2), curry2)
  399. assert raises(TypeError, lambda: add(1)(x=2)(3))
  400. """
  401. def generate_compose_test_cases():
  402. """
  403. Generate test cases for parametrized tests of the compose function.
  404. """
  405. def add_then_multiply(a, b, c=10):
  406. return (a + b) * c
  407. return (
  408. (
  409. (), # arguments to compose()
  410. (0,), {}, # positional and keyword args to the Composed object
  411. 0 # expected result
  412. ),
  413. (
  414. (inc,),
  415. (0,), {},
  416. 1
  417. ),
  418. (
  419. (double, inc),
  420. (0,), {},
  421. 2
  422. ),
  423. (
  424. (str, iseven, inc, double),
  425. (3,), {},
  426. "False"
  427. ),
  428. (
  429. (str, add),
  430. (1, 2), {},
  431. '3'
  432. ),
  433. (
  434. (str, inc, add_then_multiply),
  435. (1, 2), {"c": 3},
  436. '10'
  437. ),
  438. )
  439. def test_compose():
  440. for (compose_args, args, kw, expected) in generate_compose_test_cases():
  441. assert compose(*compose_args)(*args, **kw) == expected
  442. def test_compose_metadata():
  443. # Define two functions with different names
  444. def f(a):
  445. return a
  446. def g(a):
  447. return a
  448. composed = compose(f, g)
  449. assert composed.__name__ == 'f_of_g'
  450. if not IS_PYPY_GE_39: # pypy >=3.9 doesn't like __doc__ property
  451. assert composed.__doc__ == 'lambda *args, **kwargs: f(g(*args, **kwargs))'
  452. # Create an object with no __name__.
  453. h = object()
  454. composed = compose(f, h)
  455. assert composed.__name__ == 'Compose'
  456. if not IS_PYPY_GE_39: # pypy >=3.9 doesn't like __doc__ property
  457. assert composed.__doc__ == 'A composition of functions'
  458. assert repr(composed) == 'Compose({!r}, {!r})'.format(f, h)
  459. assert composed == compose(f, h)
  460. assert composed == AlwaysEquals()
  461. assert not composed == compose(h, f)
  462. assert not composed == object()
  463. assert not composed == NeverEquals()
  464. assert composed != compose(h, f)
  465. assert composed != NeverEquals()
  466. assert composed != object()
  467. assert not composed != compose(f, h)
  468. assert not composed != AlwaysEquals()
  469. assert hash(composed) == hash(compose(f, h))
  470. assert hash(composed) != hash(compose(h, f))
  471. bindable = compose(str, lambda x: x*2, lambda x, y=0: int(x) + y)
  472. class MyClass:
  473. def __int__(self):
  474. return 8
  475. my_method = bindable
  476. my_static_method = staticmethod(bindable)
  477. assert MyClass.my_method(3) == '6'
  478. assert MyClass.my_method(3, y=2) == '10'
  479. assert MyClass.my_static_method(2) == '4'
  480. assert MyClass().my_method() == '16'
  481. assert MyClass().my_method(y=3) == '22'
  482. assert MyClass().my_static_method(0) == '0'
  483. assert MyClass().my_static_method(0, 1) == '2'
  484. assert compose(f, h).__wrapped__ is h
  485. if hasattr(cytoolz, 'sandbox'): # only test this with Python version (i.e., not Cython)
  486. assert compose(f, h).__class__.__wrapped__ is None
  487. # __signature__ is python3 only
  488. def myfunc(a, b, c, *d, **e):
  489. return 4
  490. def otherfunc(f):
  491. return 'result: {}'.format(f)
  492. # set annotations compatibly with python2 syntax
  493. myfunc.__annotations__ = {
  494. 'a': int,
  495. 'b': str,
  496. 'c': float,
  497. 'd': int,
  498. 'e': bool,
  499. 'return': int,
  500. }
  501. otherfunc.__annotations__ = {'f': int, 'return': str}
  502. composed = compose(otherfunc, myfunc)
  503. sig = inspect.signature(composed)
  504. assert sig.parameters == inspect.signature(myfunc).parameters
  505. assert sig.return_annotation == str
  506. class MyClass:
  507. method = composed
  508. assert len(inspect.signature(MyClass().method).parameters) == 4
  509. def generate_compose_left_test_cases():
  510. """
  511. Generate test cases for parametrized tests of the compose function.
  512. These are based on, and equivalent to, those produced by
  513. enerate_compose_test_cases().
  514. """
  515. return tuple(
  516. (tuple(reversed(compose_args)), args, kwargs, expected)
  517. for (compose_args, args, kwargs, expected)
  518. in generate_compose_test_cases()
  519. )
  520. def test_compose_left():
  521. for (compose_left_args, args, kw, expected) in generate_compose_left_test_cases():
  522. assert compose_left(*compose_left_args)(*args, **kw) == expected
  523. def test_pipe():
  524. assert pipe(1, inc) == 2
  525. assert pipe(1, inc, inc) == 3
  526. assert pipe(1, double, inc, iseven) is False
  527. def test_complement():
  528. # No args:
  529. assert complement(lambda: False)()
  530. assert not complement(lambda: True)()
  531. # Single arity:
  532. assert complement(iseven)(1)
  533. assert not complement(iseven)(2)
  534. assert complement(complement(iseven))(2)
  535. assert not complement(complement(isodd))(2)
  536. # Multiple arities:
  537. both_even = lambda a, b: iseven(a) and iseven(b)
  538. assert complement(both_even)(1, 2)
  539. assert not complement(both_even)(2, 2)
  540. # Generic truthiness:
  541. assert complement(lambda: "")()
  542. assert complement(lambda: 0)()
  543. assert complement(lambda: None)()
  544. assert complement(lambda: [])()
  545. assert not complement(lambda: "x")()
  546. assert not complement(lambda: 1)()
  547. assert not complement(lambda: [1])()
  548. def test_do():
  549. inc = lambda x: x + 1
  550. assert do(inc, 1) == 1
  551. log = []
  552. assert do(log.append, 1) == 1
  553. assert log == [1]
  554. def test_juxt_generator_input():
  555. data = list(range(10))
  556. juxtfunc = juxt(itemgetter(2*i) for i in range(5))
  557. assert juxtfunc(data) == (0, 2, 4, 6, 8)
  558. assert juxtfunc(data) == (0, 2, 4, 6, 8)
  559. def test_flip():
  560. def f(a, b):
  561. return a, b
  562. assert flip(f, 'a', 'b') == ('b', 'a')
  563. def test_excepts():
  564. # These are descriptors, make sure this works correctly.
  565. assert excepts.__name__ == 'excepts'
  566. # in Python < 3.13 the second line is indented, in 3.13+
  567. # it is not, strip all lines to fudge it
  568. testlines = "\n".join((line.strip() for line in excepts.__doc__.splitlines()))
  569. assert (
  570. 'A wrapper around a function to catch exceptions and\n'
  571. 'dispatch to a handler.\n'
  572. ) in testlines
  573. def idx(a):
  574. """idx docstring
  575. """
  576. return [1, 2].index(a)
  577. def handler(e):
  578. """handler docstring
  579. """
  580. assert isinstance(e, ValueError)
  581. return -1
  582. excepting = excepts(ValueError, idx, handler)
  583. assert excepting(1) == 0
  584. assert excepting(2) == 1
  585. assert excepting(3) == -1
  586. assert excepting.__name__ == 'idx_excepting_ValueError'
  587. if not IS_PYPY_GE_39: # pypy >=3.9 doesn't like __doc__ property
  588. assert 'idx docstring' in excepting.__doc__
  589. assert 'ValueError' in excepting.__doc__
  590. assert 'handler docstring' in excepting.__doc__
  591. def getzero(a):
  592. """getzero docstring
  593. """
  594. return a[0]
  595. excepting = excepts((IndexError, KeyError), getzero)
  596. assert excepting([]) is None
  597. assert excepting([1]) == 1
  598. assert excepting({}) is None
  599. assert excepting({0: 1}) == 1
  600. assert excepting.__name__ == 'getzero_excepting_IndexError_or_KeyError'
  601. if not IS_PYPY_GE_39: # pypy >=3.9 doesn't like __doc__ property
  602. assert 'getzero docstring' in excepting.__doc__
  603. assert 'return_none' in excepting.__doc__
  604. assert 'Returns None' in excepting.__doc__
  605. def raise_(a):
  606. """A function that raises an instance of the exception type given.
  607. """
  608. raise a()
  609. excepting = excepts((ValueError, KeyError), raise_)
  610. assert excepting(ValueError) is None
  611. assert excepting(KeyError) is None
  612. assert raises(TypeError, lambda: excepting(TypeError))
  613. assert raises(NotImplementedError, lambda: excepting(NotImplementedError))
  614. excepting = excepts(object(), object(), object())
  615. assert excepting.__name__ == 'excepting'
  616. assert excepting.__doc__ == excepts.__doc__