Source code for phantom.fn
from __future__ import annotations
import functools
from functools import partial
from typing import Any
from typing import Callable
from typing import TypeVar
def _name(fn: Callable) -> str:
if isinstance(fn, partial):
fn = fn.func
try:
return fn.__qualname__
except AttributeError:
return str(fn)
AA = TypeVar("AA")
AR = TypeVar("AR")
BA = TypeVar("BA")
[docs]def compose2(a: Callable[[AA], AR], b: Callable[[BA], AA]) -> Callable[[BA], AR]:
"""
Returns a function composed from the two given functions ``a`` and ``b`` such that
calling ``compose2(a, b)(x)`` is equivalent to calling ``a(b(x))``.
>>> compose2("".join, reversed)("!olleH")
'Hello!'
"""
a_name = _name(a)
b_name = _name(b)
def c(arg: BA) -> AR:
return a(b(arg))
c.__name__ = f"{a_name}∘{b_name}"
c.__doc__ = f"Function composed as {a_name}({b_name}(_))."
return c
A = TypeVar("A")
[docs]def excepts(
exception: tuple[type[Exception], ...] | type[Exception],
negate: bool = False,
) -> Callable[[Callable[[A], Any]], Callable[[A], bool]]:
"""
Turn a unary function that raises an exception into a boolean predicate.
>>> def validate_positive(number: int) -> None:
... if number < 0: raise ValueError
>>> is_positive = excepts(ValueError)(validate_positive)
>>> is_positive(0), is_positive(-1)
(True, False)
"""
def decorator(fn: Callable[[A], Any]) -> Callable[[A], bool]:
@functools.wraps(fn)
def wrapper(arg: A) -> bool:
try:
fn(arg)
except exception:
return negate
return not negate
return wrapper
return decorator