Skip to content

Commit 3b74931

Browse files
committed
Adds RequiresContextResult combinator
1 parent 8bec724 commit 3b74931

File tree

16 files changed

+266
-8
lines changed

16 files changed

+266
-8
lines changed

docs/pages/railway.rst

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,7 @@ types like ``Failure``:
6464
transforms error to value (failure became success)
6565
that works only when container is in failed state,
6666
is the opposite of ``map`` method
67-
- :func:`~returns.primitives.container.UnwrapableFailure.alt`
67+
- :func:`~returns.primitives.container.Altable.alt`
6868
transforms error to another error
6969
that works only when container is in failed state,
7070
is the opposite of ``map`` method

returns/_generated/pointfree/rescue.pyi

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,9 @@
22

33
from typing import Callable, TypeVar, overload
44

5+
from returns.context import RequiresContextResult
56
from returns.io import IOResult
67
from returns.result import Result
7-
from returns.context import RequiresContextResult
88

99
# Result:
1010
_ValueType = TypeVar('_ValueType')
@@ -44,6 +44,6 @@ def _rescue(
4444
],
4545
) -> Callable[
4646
[RequiresContextResult[_EnvType, _ValueType, _ErrorType]],
47-
RequiresContextResult[_EnvType, _ValueType, _NewErrorType]
47+
RequiresContextResult[_EnvType, _ValueType, _NewErrorType],
4848
]:
4949
...

returns/context/requires_context_result.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,7 @@ class RequiresContextResult(
7474
We only have just one type. That's by design.
7575
7676
Different converters are also not supported for this type.
77+
Use converters inside the ``RequiresContext`` context, not outside.
7778
7879
See also:
7980
https://dev.to/gcanti/getting-started-with-fp-ts-reader-1ie5

returns/primitives/container.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -171,7 +171,7 @@ def failure(self) -> _ErrorType:
171171

172172

173173
@runtime
174-
class UnwrapableFailure(Protocol[_ValueType, _ErrorType]):
174+
class Altable(Protocol[_ValueType, _ErrorType]):
175175
"""Allows to unwrap failures."""
176176

177177
def alt(
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
# -*- coding: utf-8 -*-
2+
3+
import pytest
4+
5+
from returns.context import ContextResult, RequiresContextResult
6+
from returns.primitives.container import (
7+
Altable,
8+
Bindable,
9+
Fixable,
10+
Mappable,
11+
Rescueable,
12+
Unwrapable,
13+
)
14+
from returns.result import Failure, Success
15+
16+
17+
@pytest.mark.parametrize('container', [
18+
RequiresContextResult(lambda _: Success(1)),
19+
RequiresContextResult(lambda _: Failure(1)),
20+
RequiresContextResult.from_success(1),
21+
RequiresContextResult.from_failure(1),
22+
ContextResult.ask(),
23+
])
24+
@pytest.mark.parametrize('protocol', [
25+
Bindable,
26+
Mappable,
27+
Rescueable,
28+
Unwrapable,
29+
Altable,
30+
Fixable,
31+
])
32+
def test_protocols(container, protocol):
33+
"""Ensures that RequiresContext has all the right protocols."""
34+
assert isinstance(container, protocol)
35+
36+
37+
def test_context_map():
38+
"""Ensures that RequiresContext container supports ``.map()`` method."""
39+
context = RequiresContextResult.from_success(1.0).map(str)
40+
assert context(...) == RequiresContextResult.from_success(
41+
'1.0',
42+
)(RequiresContextResult.empty)
Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
# -*- coding: utf-8 -*-
2+
3+
from returns.context import RequiresContext
4+
from returns.context import RequiresContextResult as RCR # noqa: N814
5+
from returns.result import Failure, Result, Success
6+
7+
8+
def test_bind():
9+
"""Ensures that bind works."""
10+
def factory(inner_value: int) -> RCR[int, float, str]:
11+
if inner_value > 0:
12+
return RCR(lambda deps: Success(inner_value / deps))
13+
return RCR.from_failure(str(inner_value))
14+
15+
input_value = 5
16+
bound: RCR[int, int, str] = RCR.from_success(input_value)
17+
assert bound.bind(factory)(2) == factory(input_value)(2)
18+
assert bound.bind(factory)(2) == Success(2.5)
19+
20+
assert RCR.from_success(0).bind(
21+
factory,
22+
)(2) == factory(0)(2) == Failure('0')
23+
24+
25+
def test_bind_regular_result():
26+
"""Ensures that regular ``Result`` can be bound to ``IOResult``."""
27+
def factory(inner_value: int) -> Result[int, str]:
28+
if inner_value > 0:
29+
return Success(inner_value + 1)
30+
return Failure('nope')
31+
32+
first: RCR[int, int, str] = RCR.from_success(1)
33+
third: RCR[int, int, str] = RCR.from_failure('a')
34+
35+
assert first.bind_result(factory)(RCR.empty) == Success(2)
36+
assert RCR.from_success(0).bind_result(
37+
factory,
38+
)(RCR.empty) == Failure('nope')
39+
assert third.bind_result(factory)(RCR.empty) == Failure('a')
40+
41+
42+
def test_bind_regular_context():
43+
"""Ensures that regular ``Result`` can be bound to ``IOResult``."""
44+
def factory(inner_value: int) -> RequiresContext[int, float]:
45+
return RequiresContext(lambda deps: inner_value / deps)
46+
47+
first: RCR[int, int, str] = RCR.from_success(1)
48+
third: RCR[int, int, str] = RCR.from_failure('a')
49+
50+
assert first.bind_context(factory)(2) == Success(0.5)
51+
assert RCR.from_success(2).bind_context(
52+
factory,
53+
)(1) == Success(2.0)
54+
assert third.bind_context(factory)(1) == Failure('a')
55+
56+
57+
def test_rescue_success():
58+
"""Ensures that rescue works for Success container."""
59+
def factory(inner_value) -> RCR[int, int, str]:
60+
return RCR.from_success(inner_value * 2)
61+
62+
assert RCR.from_success(5).rescue(
63+
factory,
64+
)(0) == RCR.from_success(5)(0)
65+
assert RCR.from_failure(5).rescue(
66+
factory,
67+
)(0) == RCR.from_success(10)(0)
68+
69+
70+
def test_rescue_failure():
71+
"""Ensures that rescue works for Failure container."""
72+
def factory(inner_value) -> RCR[int, int, str]:
73+
return RCR.from_failure(inner_value * 2)
74+
75+
assert RCR.from_success(5).rescue(
76+
factory,
77+
)(0) == RCR.from_success(5)(0)
78+
assert RCR.from_failure(5).rescue(
79+
factory,
80+
)(0) == RCR.from_failure(10)(0)
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
# -*- coding: utf-8 -*-
2+
3+
from returns.context import RequiresContextResult, RequiresContextResultE
4+
5+
6+
def _function(arg: int) -> RequiresContextResultE[int, float]:
7+
if arg == 0:
8+
return RequiresContextResult.from_failure(
9+
ZeroDivisionError('Divided by 0'),
10+
)
11+
return RequiresContextResult.from_success(10 / arg)
12+
13+
14+
def test_requires_context_resulte():
15+
"""Ensures that RequiresContextResultE correctly typecast."""
16+
container: RequiresContextResult[int, float, Exception] = _function(1)
17+
assert container(0) == RequiresContextResult.from_success(10.0)(0)

0 commit comments

Comments
 (0)