Source code for monad.decorators

# -*- coding: utf-8 -*-
# Copyright (c) 2012-2015, Philip Xu <pyx@xrefactor.com>
# License: BSD New, see LICENSE for details.
"""monad.decorators - helpful decorators."""

from functools import partial, wraps

from .exceptions import ExtractError
from .types import Null
from .types import Function
from .types import Monadic
from .types import Just, Nothing
from .types import Left, Right
from .types import List
from .utils import ignore_exception_set, suppress


[docs]def function(callable_object): """Decorator that wraps a callabe into :class:`Function`. >>> to_int = function(int) >>> to_int('42') 42 >>> @function ... def puts(msg, times=1): ... while times > 0: ... print(msg) ... times -= 1 >>> puts('Hello, world', 2) Hello, world Hello, world """ return Function(callable_object)
[docs]def monadic(callable_object): """Decorator that wraps a callabe into :class:`Monadic`.""" return Monadic(callable_object)
[docs]def maybe(callable_object=None, predicate=None, nothing_on_value=Null, nothing_on_exception=Exception): """Transform a callable into a function returns a :py:class:`Maybe`. >>> parse_int = maybe(int) >>> parse_int(42) Just(42) >>> parse_int(42.0) Just(42) >>> parse_int('42') Just(42) >>> parse_int('invalid') Nothing >>> parse_pos = maybe(int, predicate=lambda i: i > 0) >>> parse_pos('42') Just(42) >>> parse_pos('-42') Nothing >>> parse_nonzero = maybe(int, nothing_on_value=0) >>> parse_nonzero('42') Just(42) >>> parse_nonzero('0') Nothing >>> @maybe(nothing_on_exception=ZeroDivisionError) ... def safe_div(a, b): ... return a / b >>> safe_div(42.0, 2) Just(21.0) >>> safe_div(42, 0) Nothing When invoked, this new function returns the return value of decorated function, wrapped in a :py:class:`Maybe` monad. ``predicate`` should be a false value, or be set to a callable. The default is ``None``. ``nothing_on_value`` can be set to any object supporting comparison against return value of the original function. The default is ``Null``, which means no checking on the return value. ``nothing_on_exception`` can be a false value, a type of exception, or a tuple of exceptions. The default is ``Exception``, which will suppress most exceptions and return ``Nothing`` instead. The returned monad will be ``Nothing`` if - ``predicate`` is set, and ``predicate(result_from_decorated_function)`` returns true value (not necessarily equal to ``True``) - ``nothing_on_value`` is set and the result from decorated function matches it, testing with ``==`` - ``nothing_on_exception`` is set and a compatible exception has been caught, the exception will be suppressed in this case - exception ``ExtractError`` has been caught, when trying to extract value from ``Nothing`` - any combination of the above Otherwise, the result will be wrapped in a :py:class:`Just`. """ if callable_object is None: return partial(maybe, predicate=predicate, nothing_on_value=nothing_on_value, nothing_on_exception=nothing_on_exception) exceptions = ignore_exception_set(ExtractError, nothing_on_exception) @wraps(callable_object) def wrapper(*args, **kwargs): """Monadic function wrapper for Maybe""" # pylint: disable = star-args with suppress(*exceptions): result = callable_object(*args, **kwargs) if nothing_on_value is not Null and result == nothing_on_value: return Nothing if predicate is not None and not predicate(result): return Nothing return Just(result) return Nothing return monadic(wrapper)
[docs]def failsafe(callable_object=None, predicate=None, left_on_value=Null, left_on_exception=Exception): """Transform a callable into a function returns an :py:class:`Either`. >>> parse_int = failsafe(int) >>> parse_int(42) Right(42) >>> parse_int(42.0) Right(42) >>> parse_int('42') Right(42) >>> parse_int('invalid') Left(ValueError(...)) >>> parse_pos = failsafe(int, predicate=lambda i: i > 0) >>> parse_pos('42') Right(42) >>> parse_pos('-42') Left(-42) >>> parse_nonzero = failsafe(int, left_on_value=0) >>> parse_nonzero('42') Right(42) >>> parse_nonzero('0') Left(0) >>> @failsafe(left_on_exception=ZeroDivisionError) ... def safe_div(a, b): ... return a / b >>> safe_div(42.0, 2) Right(21.0) >>> safe_div(42, 0) Left(ZeroDivisionError(...)) When invoked, this new function returns the return value of decorated function, wrapped in an :py:class:`Either` monad. ``predicate`` should be a false value, or be set to a callable. The default is ``None``. ``left_on_value`` can be set to any object supporting comparison against return value of the original function. The default is ``Null``, which means no checking on the return value. ``left_on_exception`` should be a false value, or a type of exception, or a tuple of exceptions. The default is ``Exception``, which will suppress most exceptions and return ``Left(exception)`` instead. The returned monad will be :py:class:`Left` if - ``predicate`` is set, and ``predicate(result_from_decorated_function)`` returns true value (not necessarily equal to ``True``) - ``left_on_value`` is set and the result from decorated function matches it, testing with ``==`` - ``left_on_exception`` is set and a compatible exception has been caught, the exception will be suppressed in this case, and the value of exception will be wrapped in a :py:class:`Left` - exception ``ExtractError`` has been caught, this could be the case, for example, trying to extract value from ``Nothing`` - any combination of the above Otherwise, the result will be wrapped in a :py:class:`Right`. """ if callable_object is None: return partial(failsafe, predicate=predicate, left_on_value=left_on_value, left_on_exception=left_on_exception) exceptions = ignore_exception_set(left_on_exception) @wraps(callable_object) def wrapper(*args, **kwargs): """Monadic function wrapper for Either""" try: result = callable_object(*args, **kwargs) if left_on_value is not Null and result == left_on_value: return Left(result) if predicate is not None and not predicate(result): return Left(result) return Right(result) except ExtractError as ex: monad = ex.monad if isinstance(monad, Left): return ex.monad else: return Left(ex) except tuple(exceptions) as ex: return Left(ex) return monadic(wrapper)
[docs]def producer(function_or_generator=None, empty_on_exception=None): """Transform a callable into a producer that when called, returns ``List``. >>> @producer ... def double(a): ... yield a ... yield a >>> List(42) >> double List(42, 42) >>> @producer ... def times(a): ... for b in List(1, 2, 3): ... yield '{}x{}={}'.format(a, b, a * b) >>> List(1, 2) >> times List('1x1=1', '1x2=2', '1x3=3', '2x1=2', '2x2=4', '2x3=6') ``function_or_generator`` can be a function that returns an iterable, or a generator. ``empty_on_exception`` can be a false value, a type of exception, or a tuple of exceptions. The default is ``None``, which will not suppress all exceptions except ``ExtractError``, in which case, an empty :py:class:`List` will be returned. """ if function_or_generator is None: return partial(producer, empty_on_exception=empty_on_exception) exceptions = ignore_exception_set(ExtractError, empty_on_exception) @wraps(function_or_generator) def wrapper(*args, **kwargs): """Monadic function wrapper for List""" # pylint: disable = star-args with suppress(*exceptions): return List.from_iterable(function_or_generator(*args, **kwargs)) return List.zero return monadic(wrapper)