Skip to content

Commit 132a577

Browse files
committed
Seems like ready
1 parent 52c7606 commit 132a577

File tree

7 files changed

+63
-40
lines changed

7 files changed

+63
-40
lines changed

dry_monads/do_notation.py

Lines changed: 5 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,16 @@
11
# -*- coding: utf-8 -*-
22

33
from functools import wraps
4-
from typing import TYPE_CHECKING, Callable, TypeVar
4+
from typing import TYPE_CHECKING, Callable, TypeVar, Union
55

6+
from dry_monads.either import Either
67
from dry_monads.primitives.exceptions import UnwrapFailedError
78

8-
if TYPE_CHECKING:
9+
if TYPE_CHECKING: # pragma: no cover
910
from dry_monads.primitives.monad import Monad # noqa: Z435, F401
1011

11-
_MonadType = TypeVar('_MonadType', bound='Monad')
12+
# We need to have this ugly type because there is no other way around it:
13+
_MonadType = TypeVar('_MonadType', bound=Union['Monad', Either])
1214

1315

1416
def do_notation(
@@ -22,19 +24,3 @@ def decorator(*args, **kwargs) -> _MonadType:
2224
except UnwrapFailedError as exc:
2325
return exc.halted_monad
2426
return decorator
25-
26-
# from dry_monads.either import Success, Failure, Either
27-
28-
# @do_notation
29-
# def test() -> Success[int]:
30-
# def example(incoming: int) -> Either[int, int]:
31-
# return Failure("abc")
32-
33-
# first = example(1).unwrap()
34-
# second = example(2).unwrap()
35-
# reveal_type(first) # dry_monads/do.py:35: error: Revealed type is 'builtins.int*'
36-
# reveal_type(second) # dry_monads/do.py:36: error: Revealed type is 'builtins.int*'
37-
# return Success(first + second)
38-
39-
# reveal_type(test()) # dry_monads/do.py:39: error: Revealed type is 'dry_monads.either.Right*[builtins.int]'
40-
# print(test())

dry_monads/either.py

Lines changed: 12 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,21 @@
11
# -*- coding: utf-8 -*-
22

33
from abc import ABCMeta, abstractmethod
4-
from typing import Callable, NoReturn, Generic, TypeVar, Any, Union, NewType
5-
from typing_extensions import Protocol
4+
from typing import Any, Callable, Generic, NoReturn, TypeVar, Union
5+
6+
from typing_extensions import final
67

78
from dry_monads.primitives.exceptions import UnwrapFailedError
8-
from dry_monads.primitives.monad import NewValueType, ValueType, Monad
9+
from dry_monads.primitives.monad import Monad, NewValueType, ValueType
910

1011
ErrorType = TypeVar('ErrorType')
1112

1213

13-
class Either(Generic[ValueType, ErrorType]):
14+
# That's the most ugly part.
15+
# We need to express `Either` with two type parameters and
16+
# Left and Right with just one parameter.
17+
# And that's how we do it. Any other and more cleaner ways are appreciated.
18+
class Either(Generic[ValueType, ErrorType], metaclass=ABCMeta):
1419
"""
1520
Represents a calculation that may either fail or succeed.
1621
@@ -23,7 +28,7 @@ class Either(Generic[ValueType, ErrorType]):
2328
_inner_value: Union[ValueType, ErrorType]
2429

2530
@abstractmethod
26-
def unwrap(self) -> ValueType:
31+
def unwrap(self) -> ValueType: # pragma: no cover
2732
"""
2833
Custom magic method to unwrap inner value from monad.
2934
@@ -33,6 +38,7 @@ def unwrap(self) -> ValueType:
3338
raise NotImplementedError()
3439

3540

41+
@final
3642
class Left(Either[Any, ErrorType], Monad[ErrorType]):
3743
"""
3844
Represents a calculation which has failed.
@@ -66,6 +72,7 @@ def unwrap(self) -> NoReturn:
6672
raise UnwrapFailedError(self)
6773

6874

75+
@final
6976
class Right(Either[ValueType, Any], Monad[ValueType]):
7077
"""
7178
Represents a calculation which has succeeded and contains the result.
@@ -121,11 +128,3 @@ def unwrap(self) -> ValueType:
121128
Result = Either
122129
Success = Right
123130
Failure = Left
124-
125-
# def function(trigger: int) -> Either[int, bool]:
126-
# if trigger > 1:
127-
# reveal_type(Success(''))
128-
# return Success('')
129-
# else:
130-
# reveal_type(Failure(''))
131-
# return Failure('-')

dry_monads/primitives/monad.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
# -*- coding: utf-8 -*-
22

33
from abc import ABCMeta, abstractmethod
4-
from typing import Any, TypeVar, Generic
4+
from typing import Any, Generic, TypeVar
55

66
ValueType = TypeVar('ValueType')
77
NewValueType = TypeVar('NewValueType')
@@ -40,12 +40,12 @@ def bind(self, function): # pragma: no cover
4040
raise NotImplementedError()
4141

4242
@abstractmethod
43-
def value_or(self, default_value):
43+
def value_or(self, default_value): # pragma: no cover
4444
"""Forces to unwrap value from monad or return a default."""
4545
raise NotImplementedError()
4646

4747
@abstractmethod
48-
def unwrap(self) -> ValueType:
48+
def unwrap(self) -> ValueType: # pragma: no cover
4949
"""
5050
Custom magic method to unwrap inner value from monad.
5151

poetry.lock

Lines changed: 3 additions & 3 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

pyproject.toml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,11 @@ authors = ["sobolevn <mail@sobolevn.me>"]
66
license = "MIT"
77

88
[tool.poetry.dependencies]
9-
python = "^3.6"
9+
python = "^3.6 || ^3.7"
10+
typing-extensions = "^3.7"
1011

1112
[tool.poetry.dev-dependencies]
1213
mypy = "^0.660.0"
13-
typing-extensions = "^3.7"
1414
attrs = "^18.2"
1515
wemake-python-styleguide = "^0.7.0"
1616
flake8-pytest = "^1.3"
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
# -*- coding: utf-8 -*-
2+
3+
from dry_monads.do_notation import do_notation
4+
from dry_monads.either import Either, Failure, Success
5+
6+
7+
@do_notation
8+
def _example(number: int) -> Either[int, str]:
9+
first = Success(1).unwrap()
10+
second = Failure('E').unwrap() if number % 2 else Success(number).unwrap()
11+
return Success(first + second)
12+
13+
14+
def test_do_notation_success():
15+
"""Ensures that do notation works well for Success."""
16+
assert _example(5) == Success(6)
17+
18+
19+
def test_do_notation_failure():
20+
"""Ensures that do notation works well for Failure."""
21+
assert _example(6) == Failure('E')

tests/test_either/test_unwrap.py

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
# -*- coding: utf-8 -*-
2+
3+
import pytest
4+
5+
from dry_monads.either import Left, Right
6+
from dry_monads.primitives.exceptions import UnwrapFailedError
7+
8+
9+
def test_unwrap_success():
10+
"""Ensures that left identity works for Right monad."""
11+
assert Right(5).unwrap() == 5
12+
13+
14+
def test_unwrap_failure():
15+
"""Ensures that left identity works for Right monad."""
16+
with pytest.raises(UnwrapFailedError):
17+
assert Left(5).unwrap()

0 commit comments

Comments
 (0)