Skip to content

Commit c0d4a22

Browse files
committed
Composes Result with IOResult
1 parent e4fd9b4 commit c0d4a22

File tree

7 files changed

+63
-22
lines changed

7 files changed

+63
-22
lines changed

README.md

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -340,22 +340,23 @@ Let's refactor it to make our
340340

341341
```python
342342
import requests
343-
from returns.io import IO, impure
344-
from returns.result import Result, safe
343+
from returns.io import IO, IOResult, impure_safe
344+
from returns.result import safe
345345
from returns.pipeline import pipe
346346
from returns.pointfree import bind
347347

348-
def fetch_user_profile(user_id: int) -> Result['UserProfile', Exception]:
348+
def fetch_user_profile(user_id: int) -> IOResult['UserProfile', Exception]:
349349
"""Fetches `UserProfile` TypedDict from foreign API."""
350350
return pipe(
351351
_make_request,
352-
# after bind: def (Result) -> Result
353-
# after IO.lift: def (IO[Result]) -> IO[Result]
354-
IO.lift(bind(_parse_json)),
352+
# before: def (Response) -> UserProfile
353+
# after safe: def (Response) -> ResultE[UserProfile]
354+
# after bind: def (ResultE[Response]) -> ResultE[UserProfile]
355+
# after lift: def (IOResultE[Response]) -> IOResultE[UserProfile]
356+
IOResult.lift_result(bind(_parse_json)),
355357
)(user_id)
356358

357-
@impure
358-
@safe
359+
@impure_safe
359360
def _make_request(user_id: int) -> requests.Response:
360361
response = requests.get('/api/users/{0}'.format(user_id))
361362
response.raise_for_status()

returns/_generated/flatten.pyi

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,6 @@ _ValueType = TypeVar('_ValueType')
1212
_ErrorType = TypeVar('_ErrorType')
1313

1414

15-
1615
@overload
1716
def _flatten(container: IO[IO[_ValueType]]) -> IO[_ValueType]:
1817
...

returns/_generated/pipeline.pyi

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

3-
from typing import Callable, Coroutine, Type, TypeVar, Union, overload
3+
from typing import Callable, Coroutine, Type, TypeVar, overload
44

55
from typing_extensions import Protocol
66

returns/converters.py

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,9 @@
11
# -*- coding: utf-8 -*-
22

3-
from typing import TypeVar, overload
3+
from typing import TypeVar
44

55
from returns._generated import fold
66
from returns._generated.flatten import _flatten as flatten # noqa: F401
7-
from returns.context import RequiresContext
8-
from returns.functions import identity
9-
from returns.io import IO
107
from returns.maybe import Maybe
118
from returns.result import Failure, Result, Success
129

returns/io.py

Lines changed: 46 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -273,6 +273,36 @@ def bind(
273273
return function(self._inner_value.unwrap())
274274
return self # type: ignore
275275

276+
def bind_result(
277+
self,
278+
function: Callable[
279+
[_ValueType],
280+
'Result[_NewValueType, _ErrorType]',
281+
],
282+
) -> 'IOResult[_NewValueType, _ErrorType]':
283+
"""
284+
Composes successful container with a function that returns a container.
285+
286+
Similar to :meth:`~IOResult.bind`, but works with containers
287+
that return :class:`returns.result.Result`
288+
instead of :class:`~IOResult`.
289+
290+
.. code:: python
291+
292+
>>> from returns.io import IOResult, IOFailure, IOSuccess
293+
>>> from returns.result import Result, Success
294+
295+
>>> def bindable(string: str) -> Result[str, str]:
296+
... return Success(string + 'b')
297+
...
298+
>>> assert IOSuccess('a').bind_result(bindable) == IOSuccess('ab')
299+
>>> assert IOFailure('a').bind_result(bindable) == IOFailure('a')
300+
301+
"""
302+
if is_successful(self._inner_value):
303+
return IOResult(function(self._inner_value.unwrap()))
304+
return self # type: ignore
305+
276306
def fix(
277307
self,
278308
function: Callable[[_ErrorType], _NewValueType],
@@ -419,6 +449,21 @@ def lift(
419449
"""
420450
return lambda container: container.map(function)
421451

452+
@classmethod
453+
def lift_result(
454+
cls,
455+
function: Callable[[_ValueType], Result[_NewValueType, _ErrorType]],
456+
) -> Callable[
457+
['IOResult[_ValueType, _ErrorType]'],
458+
'IOResult[_NewValueType, _ErrorType]',
459+
]:
460+
"""
461+
Lifts function from ``Result`` to ``IOResult`` for better composition.
462+
463+
>>> assert False
464+
"""
465+
...
466+
422467
@classmethod
423468
def from_typecast(
424469
cls, container: IO[Result[_ValueType, _ErrorType]],
@@ -469,7 +514,6 @@ def IOFailure( # noqa: N802
469514
return IOResult(Failure(inner_value))
470515

471516

472-
473517
# Aliases:
474518

475519
#: A popular case for writing `Result` is using `Exception` as the last type.
@@ -495,7 +539,7 @@ def impure_safe(
495539
"""Case for regular functions."""
496540

497541

498-
def impure_safe(function):
542+
def impure_safe(function): # noqa: C901
499543
"""
500544
Decorator to mark function that it returns :py:class:`IO` container.
501545

returns/pipeline.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,13 @@
33
from typing import TYPE_CHECKING, Union
44

55
from returns._generated.pipe import _pipe as pipe # noqa: F401
6-
from returns._generated.pipeline import _pipeline as pipeline
6+
from returns._generated.pipeline import _pipeline as pipeline # noqa: F401
77
from returns.primitives.exceptions import UnwrapFailedError
88

99
if TYPE_CHECKING: # pragma: no cover
10-
from returns.maybe import Maybe
11-
from returns.result import Result
12-
from returns.io import IOResult
10+
from returns.maybe import Maybe # noqa: WPS433
11+
from returns.result import Result # noqa: WPS433
12+
from returns.io import IOResult # noqa: WPS433
1313

1414
# Logical aliases:
1515
_Unwrapable = Union[Result, Maybe, IOResult]

tests/test_converters/test_flatten.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44

55
from returns.context import Context
66
from returns.converters import flatten
7-
from returns.io import IO, IOSuccess, IOFailure
7+
from returns.io import IO, IOFailure, IOSuccess
88
from returns.maybe import Nothing, Some
99
from returns.result import Failure, Success
1010

@@ -21,7 +21,7 @@
2121
2222
# Nope:
2323
(Failure(Failure('a')), Failure(Failure('a'))),
24-
(IOFailure(IOFailure('a')), IOFailure(IOFailure('a')))
24+
(IOFailure(IOFailure('a')), IOFailure(IOFailure('a'))),
2525
])
2626
def test_flatten(container, merged):
2727
"""Ensures that `join` is always returning the correct type."""

0 commit comments

Comments
 (0)