Skip to content

Commit

Permalink
Merge ce64f8f into 92eda55
Browse files Browse the repository at this point in the history
  • Loading branch information
sobolevn committed Jun 15, 2019
2 parents 92eda55 + ce64f8f commit ab746b3
Show file tree
Hide file tree
Showing 20 changed files with 621 additions and 12 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ We follow Semantic Versions since the `0.1.0` release.

### Features

- Reintroduces the `Maybe` monad, typed!
- Adds `mypy` plugin to type decorators
- Complete rewrite of `Result` types
- Partial API change, now `Success` and `Failure` are not types, but functions
Expand Down
16 changes: 16 additions & 0 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,22 @@ flake8 returns tests docs

These steps are mandatory during the CI.

### Fixing pytest coverage issue

Coverage does not work well with `pytest-mypy-plugin`,
that's why we have two phases of `pytest` run.

If you accidentally mess things up
and see `INTERNALERROR> coverage.misc.CoverageException` in your log,
do:

```bash
rm .coverage*
rm -rf .pytest_cache htmlcov
```

And it should solve it.
Then use correct test commands.

## Type checks

Expand Down
1 change: 1 addition & 0 deletions returns/contrib/mypy/decorator_plugin.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
_TYPED_DECORATORS = {
'returns.result.safe',
'returns.io.impure',
'returns.maybe.maybe',
}


Expand Down
171 changes: 171 additions & 0 deletions returns/maybe.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,171 @@
# -*- coding: utf-8 -*-

from abc import ABCMeta
from functools import wraps
from inspect import iscoroutinefunction
from typing import TypeVar

from returns.primitives.container import (
FixableContainer,
GenericContainerOneSlot,
ValueUnwrapContainer,
)
from returns.primitives.exceptions import UnwrapFailedError

_ValueType = TypeVar('_ValueType')


class Maybe(
GenericContainerOneSlot[_ValueType],
FixableContainer,
ValueUnwrapContainer,
metaclass=ABCMeta,
):
"""
Represents a result of a series of commutation that can return ``None``.
An alternative to using exceptions or constant ``is None`` checks.
``Maybe`` is an abstract type and should not be instantiated directly.
Instead use ``Some`` and ``Nothing``.
"""

@classmethod
def new(cls, inner_value):
"""Creates new instance of Maybe container based on a value."""
if inner_value is None:
return _Nothing(inner_value)
return _Some(inner_value)


class _Nothing(Maybe[None]): # noqa: Z214
"""Represents an empty state."""

def __init__(self, inner_value=None):
"""
Wraps the given value in the Container.
'value' can only be ``None``.
"""
object.__setattr__(self, '_inner_value', inner_value) # noqa: Z462

def __str__(self):
"""Custom str definition without state inside."""
return '<Nothing>'

def map(self, function): # noqa: A003
"""Returns the 'Nothing' instance that was used to call the method."""
return self

def bind(self, function):
"""Returns the 'Nothing' instance that was used to call the method."""
return self

def fix(self, function):
"""
Applies function to the inner value.
Applies 'function' to the contents of the 'Some' instance
and returns a new 'Some' object containing the result.
'function' should not accept any arguments
and return a non-container result.
"""
return _Some(function())

def rescue(self, function):
"""
Applies 'function' to the result of a previous calculation.
'function' should not accept any arguments
and return Maybe a 'Nothing' or 'Some' type object.
"""
return function()

def value_or(self, default_value):
"""Returns the value if we deal with 'Some' or default if 'Nothing'."""
return default_value

def unwrap(self):
"""Raises an exception, since it does not have a value inside."""
raise UnwrapFailedError(self)

def failure(self):
"""Unwraps inner error value from failed container."""
return self._inner_value


class _Some(Maybe[_ValueType]):
"""
Represents a calculation which has succeeded and contains the value.
Quite similar to ``Success`` type.
"""

def map(self, function): # noqa: A003
"""
Applies function to the inner value.
Applies 'function' to the contents of the 'Some' instance
and returns a new 'Some' object containing the result.
'function' should accept a single "normal" (non-container) argument
and return a non-container result.
"""
return _Some(function(self._inner_value))

def bind(self, function):
"""
Applies 'function' to the result of a previous calculation.
'function' should accept a single "normal" (non-container) argument
and return 'Nothing' or 'Some' type object.
"""
return function(self._inner_value)

def fix(self, function):
"""Returns the 'Some' instance that was used to call the method."""
return self

def rescue(self, function):
"""Returns the 'Some' instance that was used to call the method."""
return self

def value_or(self, default_value):
"""Returns the value if we deal with 'Some' or default if 'Nothing'."""
return self._inner_value

def unwrap(self):
"""Returns the unwrapped value from the inside of this container."""
return self._inner_value

def failure(self):
"""Raises an exception, since it does not have an error inside."""
raise UnwrapFailedError(self)


def Some(inner_value): # noqa: N802
"""Public unit function of protected `_Some` type."""
return _Some(inner_value)


#: Public unit value of protected `_Nothing` type.
Nothing = _Nothing()


def maybe(function):
"""
Decorator to covert ``None`` returning function to ``Maybe`` container.
Supports both async and regular functions.
"""
if iscoroutinefunction(function):
async def decorator(*args, **kwargs):
regular_result = await function(*args, **kwargs)
if regular_result is None:
return Nothing
return Some(regular_result)
else:
def decorator(*args, **kwargs):
regular_result = function(*args, **kwargs)
if regular_result is None:
return Nothing
return Some(regular_result)
return wraps(function)(decorator)
106 changes: 106 additions & 0 deletions returns/maybe.pyi
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
# -*- coding: utf-8 -*-

from abc import ABCMeta
from typing import Any, Callable, Coroutine, Optional, TypeVar, Union, overload

from typing_extensions import final

from returns.primitives.container import (
FixableContainer,
GenericContainerOneSlot,
ValueUnwrapContainer,
)

_ValueType = TypeVar('_ValueType')
_NewValueType = TypeVar('_NewValueType')
_ErrorType = TypeVar('_ErrorType')


class Maybe(
GenericContainerOneSlot[_ValueType],
FixableContainer,
ValueUnwrapContainer,
metaclass=ABCMeta,
):
@classmethod
def new(cls, inner_value: _ValueType) -> 'Maybe[_ValueType]':
...

def map( # noqa: A003
self,
function: Callable[[_ValueType], _NewValueType],
) -> 'Maybe[_NewValueType]':
...

def bind(
self,
function: Callable[[_ValueType], 'Maybe[_NewValueType]'],
) -> 'Maybe[_NewValueType]':
...

def fix(
self,
function: Callable[[], '_NewValueType'],
) -> 'Maybe[_NewValueType]':
...

def rescue(
self,
function: Callable[[], 'Maybe[_NewValueType]'],
) -> 'Maybe[_NewValueType]':
...

def value_or(
self,
default_value: _NewValueType,
) -> Union[_ValueType, _NewValueType]:
...

def unwrap(self) -> _ValueType:
...

def failure(self) -> None:
...


@final
class _Nothing(Maybe[Any]):
_inner_value: None

def __init__(self, inner_value: None = ...) -> None: # noqa: Z459
...


@final
class _Some(Maybe[_ValueType]):
_inner_value: _ValueType

def __init__(self, inner_value: _ValueType) -> None:
...


def Some(inner_value: _ValueType) -> Maybe[_ValueType]: # noqa: N802
...


Nothing: Maybe[Any]


@overload # noqa: Z320
def maybe( # type: ignore
function: Callable[
...,
Coroutine[_ValueType, _ErrorType, Optional[_NewValueType]],
],
) -> Callable[
...,
Coroutine[_ValueType, _ErrorType, Maybe[_NewValueType]],
]:
...


@overload
def maybe(
function: Callable[..., Optional[_NewValueType]],
) -> Callable[..., Maybe[_NewValueType]]:
...
10 changes: 5 additions & 5 deletions returns/result.py
Original file line number Diff line number Diff line change
Expand Up @@ -155,23 +155,23 @@ def safe(function): # noqa: C901
"""
Decorator to covert exception throwing function to 'Result' container.
Show be used with care, since it only catches 'Exception' subclasses.
Should be used with care, since it only catches 'Exception' subclasses.
It does not catch 'BaseException' subclasses.
Supports both async and regular functions.
"""
if iscoroutinefunction(function):
async def decorator(*args, **kwargs):
try:
return _Success(await function(*args, **kwargs))
return Success(await function(*args, **kwargs))
except Exception as exc:
return _Failure(exc)
return Failure(exc)
else:
def decorator(*args, **kwargs):
try:
return _Success(function(*args, **kwargs))
return Success(function(*args, **kwargs))
except Exception as exc:
return _Failure(exc)
return Failure(exc)
return wraps(function)(decorator)


Expand Down
7 changes: 3 additions & 4 deletions returns/result.pyi
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# -*- coding: utf-8 -*-

from abc import ABCMeta
from typing import Any, Callable, Coroutine, NoReturn, TypeVar, Union, overload
from typing import Any, Callable, Coroutine, TypeVar, Union, overload

from typing_extensions import final

Expand All @@ -22,7 +22,6 @@ _NewErrorType = TypeVar('_NewErrorType')
# Just aliases:
_FirstType = TypeVar('_FirstType')
_SecondType = TypeVar('_SecondType')
_ThirdType = TypeVar('_ThirdType')

# Hacks for functions:
_ReturnsResultType = TypeVar(
Expand Down Expand Up @@ -77,10 +76,10 @@ class Result(
) -> Union[_ValueType, _NewValueType]:
...

def unwrap(self) -> Union[_ValueType, NoReturn]:
def unwrap(self) -> _ValueType:
...

def failure(self) -> Union[_ErrorType, NoReturn]:
def failure(self) -> _ErrorType:
...


Expand Down
Loading

0 comments on commit ab746b3

Please sign in to comment.