Skip to content

Commit 1d33670

Browse files
committed
Refs #243
1 parent f295d2d commit 1d33670

File tree

7 files changed

+250
-3
lines changed

7 files changed

+250
-3
lines changed

docs/pages/context.rst

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -298,6 +298,8 @@ so you can work easily with it:
298298
allows to bind functions that return ``IOResult`` with just one call
299299
- :meth:`~RequiresContextIOResult.bind_context`
300300
allows to bind functions that return ``RequiresContext`` easily
301+
- :meth:`~RequiresContextIOResult.bind_context_result`
302+
allows to bind functions that return ``RequiresContextResult`` easily
301303
- There are also several useful contructors from any possible type
302304

303305
Use it when you work with impure context-related functions that might fail.

returns/context/requires_context_io_result.py

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
from typing_extensions import final
66

77
from returns.context.requires_context import RequiresContext
8+
from returns.context.requires_context_result import RequiresContextResult
89
from returns.io import IO, IOFailure, IOResult, IOSuccess
910
from returns.primitives.container import BaseContainer
1011
from returns.primitives.types import Immutable
@@ -273,6 +274,59 @@ def bind_context(
273274
),
274275
)
275276

277+
def bind_context_result(
278+
self,
279+
function: Callable[
280+
[_ValueType],
281+
RequiresContextResult[_EnvType, _NewValueType, _ErrorType],
282+
],
283+
) -> 'RequiresContextIOResult[_EnvType, _NewValueType, _ErrorType]':
284+
"""
285+
Binds ``RequiresContextResult`` returning function to the current one.
286+
287+
.. code:: python
288+
289+
>>> from returns.context import RequiresContextResult
290+
>>> from returns.io import IOSuccess, IOFailure
291+
>>> from returns.result import Success, Failure
292+
293+
>>> def function(arg: int) -> RequiresContextResult[str, int, int]:
294+
... if arg > 0:
295+
... return RequiresContextResult(
296+
... lambda deps: Success(len(deps) + arg),
297+
... )
298+
... return RequiresContextResult(
299+
... lambda deps: Failure(len(deps) + arg),
300+
... )
301+
...
302+
>>> assert function(2)('abc') == Success(5)
303+
>>> assert function(-1)('abc') == Failure(2)
304+
305+
>>> assert RequiresContextIOResult.from_success(
306+
... 2,
307+
... ).bind_context_result(
308+
... function,
309+
... )('abc') == IOSuccess(5)
310+
311+
>>> assert RequiresContextIOResult.from_success(
312+
... -1,
313+
... ).bind_context_result(
314+
... function,
315+
... )('abc') == IOFailure(2)
316+
317+
>>> assert RequiresContextIOResult.from_failure(
318+
... 2,
319+
... ).bind_context_result(
320+
... function,
321+
... )('abc') == IOFailure(2)
322+
323+
"""
324+
return RequiresContextIOResult(
325+
lambda deps: self(deps).bind_result(
326+
lambda inner: function(inner)(deps), # type: ignore
327+
),
328+
)
329+
276330
def bind_ioresult(
277331
self,
278332
function: Callable[[_ValueType], IOResult[_NewValueType, _ErrorType]],
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
# -*- coding: utf-8 -*-
2+
3+
import pytest
4+
5+
from returns.context import ContextIOResult, RequiresContextIOResult
6+
from returns.io import IOFailure, IOSuccess
7+
from returns.primitives.container import (
8+
Altable,
9+
Bindable,
10+
Fixable,
11+
Mappable,
12+
Rescueable,
13+
Unwrapable,
14+
)
15+
from returns.primitives.exceptions import ImmutableStateError
16+
from returns.result import Failure, Success
17+
18+
19+
@pytest.mark.parametrize('container', [
20+
RequiresContextIOResult(lambda _: IOSuccess(1)),
21+
RequiresContextIOResult(lambda _: IOFailure(1)),
22+
RequiresContextIOResult.from_success(1),
23+
RequiresContextIOResult.from_failure(1),
24+
RequiresContextIOResult.from_result(Success(1)),
25+
RequiresContextIOResult.from_result(Failure(1)),
26+
RequiresContextIOResult.from_ioresult(IOSuccess(1)),
27+
RequiresContextIOResult.from_ioresult(IOFailure(1)),
28+
ContextIOResult.ask(),
29+
])
30+
@pytest.mark.parametrize('protocol', [
31+
Bindable,
32+
Mappable,
33+
Rescueable,
34+
Unwrapable,
35+
Altable,
36+
Fixable,
37+
])
38+
def test_protocols(container, protocol):
39+
"""Ensures that RequiresContext has all the right protocols."""
40+
assert isinstance(container, protocol)
41+
42+
43+
def test_context_io_result_immutable():
44+
"""Ensures that helper is immutable."""
45+
with pytest.raises(ImmutableStateError):
46+
ContextIOResult().abc = 1
47+
48+
49+
def test_requires_context_result_immutable():
50+
"""Ensures that container is immutable."""
51+
with pytest.raises(ImmutableStateError):
52+
RequiresContextIOResult.from_success(1).abc = 1
Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,114 @@
1+
# -*- coding: utf-8 -*-
2+
3+
from returns.context import RequiresContext
4+
from returns.context import RequiresContextIOResult as RCR # noqa: N814
5+
from returns.context import RequiresContextResult
6+
from returns.io import IOFailure, IOResult, IOSuccess
7+
from returns.result import Failure, Result, Success
8+
9+
10+
def test_bind():
11+
"""Ensures that bind works."""
12+
def factory(inner_value: int) -> RCR[int, float, str]:
13+
if inner_value > 0:
14+
return RCR(lambda deps: IOSuccess(inner_value / deps))
15+
return RCR.from_failure(str(inner_value))
16+
17+
input_value = 5
18+
bound: RCR[int, int, str] = RCR.from_success(input_value)
19+
assert bound.bind(factory)(2) == factory(input_value)(2)
20+
assert bound.bind(factory)(2) == IOSuccess(2.5)
21+
22+
assert RCR.from_success(0).bind(
23+
factory,
24+
)(2) == factory(0)(2) == IOFailure('0')
25+
26+
27+
def test_bind_regular_result():
28+
"""Ensures that regular ``Result`` can be bound."""
29+
def factory(inner_value: int) -> Result[int, str]:
30+
if inner_value > 0:
31+
return Success(inner_value + 1)
32+
return Failure('nope')
33+
34+
first: RCR[int, int, str] = RCR.from_success(1)
35+
third: RCR[int, int, str] = RCR.from_failure('a')
36+
37+
assert first.bind_result(factory)(RCR.empty) == IOSuccess(2)
38+
assert RCR.from_success(0).bind_result(
39+
factory,
40+
)(RCR.empty) == IOFailure('nope')
41+
assert third.bind_result(factory)(RCR.empty) == IOFailure('a')
42+
43+
44+
def test_bind_io_result():
45+
"""Ensures that io ``Result`` can be bound."""
46+
def factory(inner_value: int) -> IOResult[int, str]:
47+
if inner_value > 0:
48+
return IOSuccess(inner_value + 1)
49+
return IOFailure('nope')
50+
51+
first: RCR[int, int, str] = RCR.from_success(1)
52+
third: RCR[int, int, str] = RCR.from_failure('a')
53+
54+
assert first.bind_ioresult(factory)(RCR.empty) == IOSuccess(2)
55+
assert RCR.from_success(0).bind_ioresult(
56+
factory,
57+
)(RCR.empty) == IOFailure('nope')
58+
assert third.bind_ioresult(factory)(RCR.empty) == IOFailure('a')
59+
60+
61+
def test_bind_regular_context():
62+
"""Ensures that regular ``RequiresContext`` can be bound."""
63+
def factory(inner_value: int) -> RequiresContext[int, float]:
64+
return RequiresContext(lambda deps: inner_value / deps)
65+
66+
first: RCR[int, int, str] = RCR.from_success(1)
67+
third: RCR[int, int, str] = RCR.from_failure('a')
68+
69+
assert first.bind_context(factory)(2) == IOSuccess(0.5)
70+
assert RCR.from_success(2).bind_context(
71+
factory,
72+
)(1) == IOSuccess(2.0)
73+
assert third.bind_context(factory)(1) == IOFailure('a')
74+
75+
76+
def test_bind_result_context():
77+
"""Ensures that ``RequiresContextResult`` can be bound."""
78+
def factory(inner_value: int) -> RequiresContextResult[int, float, str]:
79+
return RequiresContextResult(lambda deps: Success(inner_value / deps))
80+
81+
first: RCR[int, int, str] = RCR.from_success(1)
82+
third: RCR[int, int, str] = RCR.from_failure('a')
83+
84+
assert first.bind_context_result(factory)(2) == IOSuccess(0.5)
85+
assert RCR.from_success(2).bind_context_result(
86+
factory,
87+
)(1) == IOSuccess(2.0)
88+
assert third.bind_context_result(factory)(1) == IOFailure('a')
89+
90+
91+
def test_rescue_success():
92+
"""Ensures that rescue works for Success container."""
93+
def factory(inner_value) -> RCR[int, int, str]:
94+
return RCR.from_success(inner_value * 2)
95+
96+
assert RCR.from_success(5).rescue(
97+
factory,
98+
)(0) == RCR.from_success(5)(0)
99+
assert RCR.from_failure(5).rescue(
100+
factory,
101+
)(0) == RCR.from_success(10)(0)
102+
103+
104+
def test_rescue_failure():
105+
"""Ensures that rescue works for Failure container."""
106+
def factory(inner_value) -> RCR[int, int, str]:
107+
return RCR.from_failure(inner_value * 2)
108+
109+
assert RCR.from_success(5).rescue(
110+
factory,
111+
)(0) == RCR.from_success(5)(0)
112+
assert RCR.from_failure(5).rescue(
113+
factory,
114+
)(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 RequiresContextIOResult, RequiresContextIOResultE
4+
5+
6+
def _function(arg: int) -> RequiresContextIOResultE[int, float]:
7+
if arg == 0:
8+
return RequiresContextIOResult.from_failure(
9+
ZeroDivisionError('Divided by 0'),
10+
)
11+
return RequiresContextIOResult.from_success(10 / arg)
12+
13+
14+
def test_requires_context_io_resulte():
15+
"""Ensures that RequiresContextResultE correctly typecast."""
16+
container: RequiresContextIOResult[int, float, Exception] = _function(1)
17+
assert container(0) == RequiresContextIOResult.from_success(10.0)(0)

tests/test_context/test_requires_context_result/test_context_result.py

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,8 @@
2020
RequiresContextResult(lambda _: Failure(1)),
2121
RequiresContextResult.from_success(1),
2222
RequiresContextResult.from_failure(1),
23+
RequiresContextResult.from_result(Success(1)),
24+
RequiresContextResult.from_result(Failure(1)),
2325
ContextResult.ask(),
2426
])
2527
@pytest.mark.parametrize('protocol', [
@@ -36,6 +38,12 @@ def test_protocols(container, protocol):
3638

3739

3840
def test_context_result_immutable():
39-
"""Ensures that RequiresContext container supports ``.map()`` method."""
41+
"""Ensures that helper is immutable."""
4042
with pytest.raises(ImmutableStateError):
4143
ContextResult().abc = 1
44+
45+
46+
def test_requires_context_result_immutable():
47+
"""Ensures that container is immutable."""
48+
with pytest.raises(ImmutableStateError):
49+
RequiresContextResult.from_success(1).abc = 1

tests/test_context/test_requires_context_result/test_requires_context_result_bind.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ def factory(inner_value: int) -> RCR[int, float, str]:
2323

2424

2525
def test_bind_regular_result():
26-
"""Ensures that regular ``Result`` can be bound to ``IOResult``."""
26+
"""Ensures that regular ``Result`` can be bound."""
2727
def factory(inner_value: int) -> Result[int, str]:
2828
if inner_value > 0:
2929
return Success(inner_value + 1)
@@ -40,7 +40,7 @@ def factory(inner_value: int) -> Result[int, str]:
4040

4141

4242
def test_bind_regular_context():
43-
"""Ensures that regular ``Result`` can be bound to ``IOResult``."""
43+
"""Ensures that regular ``RequiresContext`` can be bound."""
4444
def factory(inner_value: int) -> RequiresContext[int, float]:
4545
return RequiresContext(lambda deps: inner_value / deps)
4646

0 commit comments

Comments
 (0)