Skip to content

Commit 8bec724

Browse files
committed
Adds rescue and bind, fixes docs and tests
1 parent 246d284 commit 8bec724

File tree

17 files changed

+145
-29
lines changed

17 files changed

+145
-29
lines changed

CHANGELOG.md

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -25,9 +25,7 @@ See [0Ver](https://0ver.org/).
2525
- Adds `RequiresContextResult` container
2626
- Adds `RequiresContextResultE` alias
2727
for `RequiresContextResult[..., ..., Exception]`
28-
- Adds `RequiresContextResult` support for `bind`
29-
- Adds `RequiresContextResult` support for `flatten`
30-
- Adds `RequiresContextResult` support for `fold`
28+
- Adds `RequiresContextResult` support for `bind` and `rescue`
3129

3230
- Adds `IOResult` helper to work better with `IO[Result[a, b]]`
3331
- Adds `IOResultE` alias for `IOResult[a, Exception]`

README.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -203,6 +203,9 @@ And now you can pass your dependencies in a really direct and explicit way.
203203
And have the type-safety to check what you pass to cover your back.
204204
Check out [RequiresContext](https://returns.readthedocs.io/en/latest/pages/context.html) docs for more. There you will learn how to make `'.'` also configurable.
205205

206+
We also have [RequiresContextResult](https://returns.readthedocs.io/en/latest/pages/context.html#requirescontextresult-container)
207+
for context-related operations that might fail.
208+
206209

207210
## Result container
208211

docs/pages/container.rst

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ while maintaining the execution context.
1010
List of supported containers:
1111

1212
- :class:`Maybe <returns.maybe.Maybe>` to handle ``None`` cases
13-
- :class:`RequiresContext <returns.context.RequiresContext>`
13+
- :class:`RequiresContext <returns.context.requires_context.RequiresContext>`
1414
to pass context to your functions
1515
- :class:`IO <returns.io.IO>` to mark explicit ``IO`` actions
1616
- :class:`Result <returns.result.Result>` to handle possible exceptions

docs/pages/context.rst

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -262,13 +262,13 @@ Which means that it is a wrapper around pure function that might fail.
262262
We also added a lot of useful methods for this container,
263263
so you can work easily with it:
264264

265-
- :meth:`returns.context.RequiresContextResult.from_typecast` turns
266-
accidental ``RequiresContext[env, Result[a, b]]`` into
265+
- :meth:`returns.context.requires_context.RequiresContextResult.from_typecast`
266+
turns accidental ``RequiresContext[env, Result[a, b]]`` into
267267
full-featured ``RequiresContextResult[env, a, b]``
268-
- :meth:`returns.context.RequiresContextResult.bind_result` allows to bind
269-
functions that return ``Result`` with just one call
270-
- :meth:returns.context.RequiresContextResult.bind_context` allows to bind
271-
functions that return ``RequiresContext`` easily
268+
- :meth:`returns.context.requires_context.RequiresContextResult.bind_result`
269+
allows to bind functions that return ``Result`` with just one call
270+
- :meth:returns.context.requires_context.RequiresContextResult.bind_context`
271+
allows to bind functions that return ``RequiresContext`` easily
272272
- There are also several useful contructors from any possible type
273273

274274
Use it when you work with pure context-related functions that might fail.
@@ -288,7 +288,6 @@ How can I access dependencies inside the context?
288288

289289

290290

291-
292291
Further reading
293292
---------------
294293

returns/_generated/pointfree/bind.pyi

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
from typing import Callable, TypeVar, overload
44

5-
from returns.context import RequiresContext
5+
from returns.context import RequiresContext, RequiresContextResult
66
from returns.io import IO, IOResult
77
from returns.maybe import Maybe
88
from returns.result import Result
@@ -39,6 +39,19 @@ def _bind(
3939
...
4040

4141

42+
@overload
43+
def _bind(
44+
function: Callable[
45+
[_ValueType],
46+
RequiresContextResult[_EnvType, _NewValueType, _ErrorType],
47+
],
48+
) -> Callable[
49+
[RequiresContextResult[_EnvType, _ValueType, _ErrorType]],
50+
RequiresContextResult[_EnvType, _NewValueType, _ErrorType],
51+
]:
52+
...
53+
54+
4255
@overload
4356
def _bind(
4457
function: Callable[[_ValueType], Result[_NewValueType, _ErrorType]],

returns/_generated/pointfree/rescue.pyi

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,17 @@ from typing import Callable, TypeVar, overload
44

55
from returns.io import IOResult
66
from returns.result import Result
7+
from returns.context import RequiresContextResult
78

9+
# Result:
810
_ValueType = TypeVar('_ValueType')
911
_ErrorType = TypeVar('_ErrorType')
1012
_NewValueType = TypeVar('_NewValueType')
1113
_NewErrorType = TypeVar('_NewErrorType')
1214

15+
# Context:
16+
_EnvType = TypeVar('_EnvType')
17+
1318

1419
@overload
1520
def _rescue(
@@ -29,3 +34,16 @@ def _rescue(
2934
IOResult[_ValueType, _NewErrorType],
3035
]:
3136
...
37+
38+
39+
@overload
40+
def _rescue(
41+
function: Callable[
42+
[_ErrorType],
43+
RequiresContextResult[_EnvType, _ValueType, _NewErrorType],
44+
],
45+
) -> Callable[
46+
[RequiresContextResult[_EnvType, _ValueType, _ErrorType]],
47+
RequiresContextResult[_EnvType, _ValueType, _NewErrorType]
48+
]:
49+
...

returns/context/requires_context_result.py

Lines changed: 16 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -28,12 +28,19 @@ class RequiresContextResult(
2828
"""
2929
The ``RequiresContextResult`` combinator.
3030
31-
See :class:`returns.context.RequiresContext` for more docs.
31+
See :class:`returns.context.requires_context.RequiresContext` for more docs.
3232
3333
This is just a handy wrapper around ``RequiresContext[env, Result[a, b]]``
3434
which represents a context-dependent pure operation
3535
that might fail and return :class:`returns.result.Result`.
3636
37+
It has several important differences from the regular ``Result`` classes.
38+
It does not have ``Success`` and ``Failure`` subclasses.
39+
Because, the computation is not yet performed.
40+
And we cannot know the type in advance.
41+
42+
So, this is a thin wrapper, without any changes in logic.
43+
3744
Why do we need this wrapper? That's just for better usability!
3845
3946
.. code:: python
@@ -60,18 +67,20 @@ class RequiresContextResult(
6067
- ``RequiresContext`` values and pure functions returning it
6168
- ``Result`` and functions returning it
6269
63-
See also:
64-
https://dev.to/gcanti/getting-started-with-fp-ts-reader-1ie5
65-
https://en.wikipedia.org/wiki/Lazy_evaluation
66-
https://bit.ly/2R8l4WK
67-
https://bit.ly/2RwP4fp
68-
6970
Imporatant implementation detail:
7071
due it is meaning, ``RequiresContextResult``
7172
cannot have ``Success`` and ``Failure`` subclasses.
7273
7374
We only have just one type. That's by design.
7475
76+
Different converters are also not supported for this type.
77+
78+
See also:
79+
https://dev.to/gcanti/getting-started-with-fp-ts-reader-1ie5
80+
https://en.wikipedia.org/wiki/Lazy_evaluation
81+
https://bit.ly/2R8l4WK
82+
https://bit.ly/2RwP4fp
83+
7584
"""
7685

7786
#: This field has an extra 'RequiresContext' just because `mypy` needs it.

tests/test_pointfree/test_bind.py

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# -*- coding: utf-8 -*-
22

3-
from returns.context import RequiresContext
3+
from returns.context import RequiresContext, RequiresContextResult
44
from returns.io import IO, IOFailure, IOResult, IOSuccess
55
from returns.maybe import Maybe, Nothing, Some
66
from returns.pointfree import bind
@@ -27,6 +27,12 @@ def _context_function(argument: int) -> RequiresContext[int, int]:
2727
return RequiresContext(lambda other: argument + other)
2828

2929

30+
def _context_result_function(
31+
argument: int,
32+
) -> RequiresContextResult[int, int, str]:
33+
return RequiresContextResult(lambda other: Success(argument + other))
34+
35+
3036
def test_bind_with_io():
3137
"""Ensures that functions can be composed and return type is correct."""
3238
binded = bind(_io_function)
@@ -63,3 +69,10 @@ def test_bind_with_context():
6369
binded = bind(_context_function)
6470

6571
assert binded(RequiresContext(lambda _: 3))(5) == 8
72+
73+
74+
def test_bind_with_context_result():
75+
"""Ensures that functions can be composed and return type is correct."""
76+
binded = bind(_context_result_function)
77+
78+
assert binded(RequiresContextResult.from_success(3))(5) == Success(8)

tests/test_pointfree/test_rescue.py

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
from returns.io import IOFailure, IOResult, IOSuccess
44
from returns.pointfree import rescue
55
from returns.result import Failure, Result, Success
6+
from returns.context import RequiresContextResult
67

78

89
def _result_function(argument: int) -> Result[int, str]:
@@ -17,6 +18,14 @@ def _ioresult_function(argument: int) -> IOResult[int, str]:
1718
return IOFailure('nope')
1819

1920

21+
def _context_result_function(
22+
argument: int,
23+
) -> RequiresContextResult[int, int, str]:
24+
if argument > 0:
25+
return RequiresContextResult(lambda deps: Success(argument + deps))
26+
return RequiresContextResult.from_failure('nope')
27+
28+
2029
def test_rescue_with_ioresult():
2130
"""Ensures that functions can be composed and return type is correct."""
2231
rescued = rescue(_ioresult_function)
@@ -33,3 +42,18 @@ def test_rescue_with_result():
3342
assert rescued(Success(1)) == Success(1)
3443
assert rescued(Failure(1)) == Success(2)
3544
assert rescued(Failure(0)) == Failure('nope')
45+
46+
47+
def test_rescue_with_context_result():
48+
"""Ensures that functions can be composed and return type is correct."""
49+
rescued = rescue(_context_result_function)
50+
51+
assert rescued(
52+
RequiresContextResult.from_success(1),
53+
)(1) == Success(1)
54+
assert rescued(
55+
RequiresContextResult.from_failure(1),
56+
)(1) == Success(2)
57+
assert rescued(
58+
RequiresContextResult.from_failure(0),
59+
)(1) == Failure('nope')

typesafety/test_context/test_contest.yml renamed to typesafety/test_context/test_requires_context/test_contest.yml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,20 +3,20 @@
33
main: |
44
from returns.context import Context
55
6-
reveal_type(Context.ask()) # N: Revealed type is 'returns.context.RequiresContext[<nothing>, <nothing>]'
6+
reveal_type(Context.ask()) # N: Revealed type is 'returns.context.requires_context.RequiresContext[<nothing>, <nothing>]'
77
88
99
- case: context_ask2
1010
disable_cache: true
1111
main: |
1212
from returns.context import Context
1313
14-
reveal_type(Context[str].ask()) # N: Revealed type is 'returns.context.RequiresContext[builtins.str*, builtins.str*]'
14+
reveal_type(Context[str].ask()) # N: Revealed type is 'returns.context.requires_context.RequiresContext[builtins.str*, builtins.str*]'
1515
1616
1717
- case: requires_context_from_value
1818
disable_cache: true
1919
main: |
2020
from returns.context import RequiresContext
2121
22-
reveal_type(RequiresContext.from_value(1)) # N: Revealed type is 'returns.context.RequiresContext[Any, builtins.int*]'
22+
reveal_type(RequiresContext.from_value(1)) # N: Revealed type is 'returns.context.requires_context.RequiresContext[Any, builtins.int*]'

0 commit comments

Comments
 (0)