Skip to content

Commit

Permalink
Merge 566f090 into 1e9805a
Browse files Browse the repository at this point in the history
  • Loading branch information
matyama committed Jun 24, 2019
2 parents 1e9805a + 566f090 commit 0eb5a6f
Show file tree
Hide file tree
Showing 2 changed files with 105 additions and 1 deletion.
3 changes: 3 additions & 0 deletions README.md
Expand Up @@ -13,7 +13,10 @@ Moreover, there are general package-level functions implemented in `__init__.py`

| Function | Description |
|----------|-------------|
| `attempt(ex, f, args, kwargs)` | equivalent to `try_except` with `g = lambda _: None` |
| `chain(*fs)` | reversed function composition `chain(f, g) = g o f` |
| `silence(errors)` | decorator that silences (selected/all) errors raised by decorated function |
| `try_apply(f, args, kwargs)` | equivalent to `attempt` with `ex = Exception` |
| `try_except(ex, f, g, args, kwargs)` | `f(args, kwargs)` and on exception(s) `ex` fallback to `g(args, kwargs)` |

The package content is organized into modules by individual type class:
Expand Down
103 changes: 102 additions & 1 deletion ftoolz/functoolz/__init__.py
@@ -1,7 +1,10 @@
from typing import Any, Callable, Tuple, Type, TypeVar, Union
import functools
from typing import Any, Callable, Optional, Tuple, Type, TypeVar, Union

from cytoolz import compose

from ftoolz.typing import Map

# Invariant
A = TypeVar('A')
B = TypeVar('B')
Expand All @@ -21,6 +24,42 @@
D_out = TypeVar('D_out', covariant=True)


def attempt(
e: Union[Type[Exception], Tuple[Type[Exception], ...]],
f: Callable[..., A],
*args: Any,
**kwargs: Any
) -> Optional[A]:
"""
Attempt to return `f(args, kwargs)` and on errors `e` fallback to `None`.
>>> attempt(ValueError, int, '1')
1
>>> attempt(ValueError, int, 'a')
One can pass both args and kwargs to `f` via `attempt`.
>>> def f(x: int, y: str) -> int:
... return x + int(y)
>>> attempt(ValueError, f, 1, y='2')
3
>>> attempt(ValueError, f, 1, y='x')
Errors that are not mentioned in `e` are propagated to outer scope.
>>> attempt(ValueError, lambda d: d['k'], {'a': 1})
Traceback (most recent call last):
...
KeyError: 'k'
"""

def none(*_args: Any, **_kwargs: Any) -> Optional[A]:
return None

return try_except(e, f, none, *args, **kwargs)


def chain(*fs: Callable) -> Callable:
"""
Compose given functions in reversed order.
Expand Down Expand Up @@ -50,6 +89,68 @@ def chain(*fs: Callable) -> Callable:
return g


def silenced(
_f: Optional[Callable[..., A]] = None,
*,
error: Union[Type[Exception], Tuple[Type[Exception], ...]] = Exception
) -> Callable[[Callable[..., A]], Callable[..., Optional[A]]]:
"""
Decorator that turns `errors` raised by decorated function into `None`.
>>> @silenced(error=ValueError)
... def f(x: int, y: str) -> int:
... return x + int(y)
>>> f(1, y='2')
3
>>> f(1, y='x')
Errors that are not mentioned in `error` are propagated to outer scope.
>>> @silenced(error=ValueError)
... def g(d: Map[str, Any]) -> Any:
... return d['k']
>>> g({'a': 1})
Traceback (most recent call last):
...
KeyError: 'k'
By default any `Exception` is silenced.
>>> @silenced
... def h(d: Map[str, Any]) -> Any:
... return d['k']
>>> h({'a': 42})
"""

def decorator(f: Callable[..., A]) -> Callable[..., Optional[A]]:
@functools.wraps(f)
def wrapper(*args: Any, **kwargs: Any) -> Optional[A]:
return attempt(error, f, *args, **kwargs)

return wrapper

return decorator if _f is None else decorator(_f) # type: ignore


def try_apply(f: Callable[..., A], *args: Any, **kwargs: Any) -> Optional[A]:
"""
Run `f(args, kwargs)` and on **any** error fallback to `None`.
>>> def f(x: int, y: str) -> int:
... return x + int(y)
>>> try_apply(f, 1, y='2')
3
>>> try_apply(f, 1, y='x')
>>> try_apply(lambda d: d['k'], {'a': 1})
"""
return attempt(Exception, f, *args, **kwargs)


def try_except(
e: Union[Type[Exception], Tuple[Type[Exception], ...]],
f: Callable[..., A],
Expand Down

0 comments on commit 0eb5a6f

Please sign in to comment.