Skip to content

Commit 93e6a71

Browse files
committed
Closes #242
1 parent 3b67582 commit 93e6a71

File tree

14 files changed

+237
-22
lines changed

14 files changed

+237
-22
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@ See [0Ver](https://0ver.org/).
4949
- Adds `Unitable` protocol and `.from_success()` and `.from_failure()`
5050
methods for all `Result` realted classes
5151
- Adds `flow` function, which is similar to `pipe`
52+
- Adds `swap` coverter for `Result` and `IOResult`
5253

5354

5455
### Bugfixes

docs/pages/converters.rst

Lines changed: 57 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -12,25 +12,25 @@ Maybe and Result
1212

1313
We have two converters to work with ``Result <-> Maybe`` tranformations:
1414

15-
- ``maybe_to_result`` that converts ``Maybe`` to ``Result``
16-
- ``result_to_maybe`` that converts ``Result`` to ``Maybe``
15+
.. currentmodule:: returns.converters
16+
17+
- :func:`~.maybe_to_result` that converts ``Maybe`` to ``Result``
18+
- :func:`~.result_to_maybe` that converts ``Result`` to ``Maybe``
1719

1820
That's how they work:
1921

2022
.. code:: python
2123
2224
>>> from returns.converters import maybe_to_result, result_to_maybe
23-
>>> from returns.maybe import Maybe
25+
>>> from returns.maybe import Maybe, Some
2426
>>> from returns.result import Result, Success
2527
2628
>>> result: Result[int, Exception] = Success(1)
2729
>>> maybe: Maybe[int] = result_to_maybe(result)
28-
>>> str(maybe)
29-
'<Some: 1>'
30+
>>> assert maybe == Some(1)
3031
3132
>>> new_result: Result[int, None] = maybe_to_result(maybe)
32-
>>> str(new_result)
33-
'<Success: 1>'
33+
>>> assert new_result == Success(1)
3434
3535
Take a note, that type changes.
3636
Also, take a note that ``Success(None)`` will be converted to ``Nothing``.
@@ -39,7 +39,9 @@ Also, take a note that ``Success(None)`` will be converted to ``Nothing``.
3939
flatten
4040
-------
4141

42-
You can also use ``flatten`` to merge nested containers together:
42+
You can also use
43+
:func:`flatten <returns.converters.flatten>`
44+
to merge nested containers together:
4345

4446
.. code:: python
4547
@@ -53,12 +55,44 @@ You can also use ``flatten`` to merge nested containers together:
5355
>>> assert flatten(Success(Success(1))) == Success(1)
5456
5557
58+
swap
59+
----
60+
61+
You can use :func:`swap <returns.converters.swap>`
62+
to swap value and error types in ``Result`` and ``IOResult`` containers.
63+
64+
In other words: ``swap(Result[a, b]) == Result[b, a]``.
65+
66+
This is useful for error handling and composition.
67+
Here's an example of how ``swap`` works:
68+
69+
.. code:: python
70+
71+
>>> from returns.converters import swap
72+
>>> from returns.io import IOSuccess, IOFailure
73+
>>> from returns.result import Success, Failure
74+
75+
>>> assert swap(IOSuccess(1)) == IOFailure(1)
76+
>>> assert swap(Failure(2)) == Success(2)
77+
78+
You can use ``swap`` twice to get the same object back!
79+
80+
.. code:: python
81+
82+
>>> from returns.converters import swap
83+
>>> from returns.io import IOSuccess
84+
85+
>>> assert swap(swap(IOSuccess(1))) == IOSuccess(1)
86+
87+
5688
coalesce
5789
--------
5890

59-
You can use :func:`returns.converters.coalesce_result`
60-
and :func:`returns.converters.coalesce_maybe` converters
61-
to covert containers to a regular value.
91+
You can use
92+
:func:`coalesce_result <returns.converters.coalesce_result>`,
93+
:func:`coalesce_ioresult <returns.converters.coalesce_ioresult>`,
94+
and :func:`coalesce_maybe <returns.converters.coalesce_maybe>`
95+
converters to covert containers to a regular value.
6296

6397
These functions accept two functions:
6498
one for successful case, one for failing case.
@@ -70,9 +104,11 @@ one for successful case, one for failing case.
70104
71105
>>> def handle_success(state: int) -> float:
72106
... return state / 2
107+
...
73108
74109
>>> def handle_failure(state: str) -> float:
75110
... return 0.0
111+
...
76112
77113
>>> coalesce_result(handle_success, handle_failure)(Success(1))
78114
0.5
@@ -83,5 +119,15 @@ one for successful case, one for failing case.
83119
API Reference
84120
-------------
85121

122+
.. autofunction:: returns.converters.swap
123+
124+
.. autofunction:: returns.converters.flatten
125+
126+
.. autofunction:: returns.converters.coalesce_maybe
127+
128+
.. autofunction:: returns.converters.coalesce_result
129+
130+
.. autofunction:: returns.converters.coalesce_ioresult
131+
86132
.. automodule:: returns.converters
87133
:members:

docs/pages/pipeline.rst

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -318,7 +318,11 @@ Further reading
318318
API Reference
319319
-------------
320320

321+
.. autofunction:: returns.pipeline.flow
322+
321323
.. autofunction:: returns.pipeline.pipe
322324

325+
.. autofunction:: returns.pipeline.pipeline
326+
323327
.. automodule:: returns.pipeline
324328
:members:
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
# -*- coding: utf-8 -*-

returns/_generated/converters/swap.py

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
# -*- coding: utf-8 -*-
2+
3+
from typing import TypeVar, overload
4+
5+
from returns.io import IOResult
6+
from returns.result import Result
7+
8+
_ValueType = TypeVar('_ValueType')
9+
_ErrorType = TypeVar('_ErrorType')
10+
11+
12+
@overload
13+
def _swap(
14+
container: Result[_ValueType, _ErrorType],
15+
) -> Result[_ErrorType, _ValueType]:
16+
"""Case for Result."""
17+
18+
19+
@overload
20+
def _swap(
21+
container: IOResult[_ValueType, _ErrorType],
22+
) -> IOResult[_ErrorType, _ValueType]:
23+
"""Case for IOResult."""
24+
25+
26+
def _swap(container):
27+
"""
28+
Swaps value and error types in a container.
29+
30+
Why? Because we have a lot of ``.bind`` related helpers
31+
and none ``.rescue`` related helpers.
32+
33+
So, you can ``swap`` a container to handle errors in a simple way,
34+
and then swap it back to continue normal execution.
35+
36+
.. code:: python
37+
38+
>>> from returns.converters import swap
39+
>>> from returns.io import IOResult, IOSuccess, IOFailure
40+
41+
>>> container: IOResult[int, str] = IOSuccess(1)
42+
>>> swapped: IOResult[str, int] = swap(container)
43+
>>> assert swapped == IOFailure(1)
44+
45+
>>> container: IOResult[int, str] = IOFailure('error')
46+
>>> assert swap(container) == IOSuccess('error')
47+
48+
And here's how you can handle errors easily:
49+
50+
.. code:: python
51+
52+
>>> from returns.converters import swap
53+
>>> from returns.io import IOResult, IOSuccess
54+
>>> from returns.result import Result, Success
55+
56+
>>> def function(error: str) -> Result[str, int]:
57+
... return Success('Very bad error: ' + error)
58+
...
59+
60+
>>> container: IOResult[int, str] = IOFailure('boom')
61+
>>> # You can `.rescue_result`, but you can `.bind_result` instead!
62+
>>> assert swap(
63+
... swap(container).bind_result(function),
64+
... ) == IOFailure('Very bad error: boom')
65+
66+
This converter supports only containers
67+
that have ``.success_type`` property.
68+
69+
Basically ``Result`` and ``IOResult``.
70+
71+
"""
72+
if isinstance(container, container.success_type):
73+
return container.bind(lambda inner: container.from_failure(inner))
74+
return container.rescue(lambda inner: container.from_success(inner))

returns/converters.py

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

33
from typing import TypeVar
44

5-
from returns._generated import coalesce
6-
from returns._generated.flatten import _flatten as flatten # noqa: F401
5+
from returns._generated.converters import coalesce
6+
from returns._generated.converters.swap import _swap as swap # noqa: F401
77
from returns.maybe import Maybe
88
from returns.result import Failure, Result, Success
99

10+
from returns._generated.converters.flatten import ( # isort:skip # noqa: F401
11+
_flatten as flatten,
12+
)
13+
1014
# Contianer internals:
1115
_ValueType = TypeVar('_ValueType')
1216
_ErrorType = TypeVar('_ErrorType')

returns/pipeline.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ def is_successful(container: '_Unwrapable') -> bool:
3939
4040
This function can work with containers that support
4141
:class:`returns.primitives.interfaces.Unwrapable` protocol.
42+
But only non-lazy containers are supported.
4243
4344
"""
4445
try:

tests/test_converters/test_flatten.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@
2828
(IOFailure(IOFailure('a')), IOFailure(IOFailure('a'))),
2929
])
3030
def test_flatten(container, merged):
31-
"""Ensures that `join` is always returning the correct type."""
31+
"""Ensures that `flatten` is always returning the correct type."""
3232
assert flatten(container) == merged
3333

3434

tests/test_converters/test_swap.py

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
# -*- coding: utf-8 -*-
2+
3+
import pytest
4+
5+
from returns.converters import swap
6+
from returns.io import IOFailure, IOSuccess
7+
from returns.result import Failure, Success
8+
9+
10+
@pytest.mark.parametrize(('container', 'swapped'), [
11+
(Success({}), Failure({})),
12+
(IOSuccess(1), IOFailure(1)),
13+
14+
(Failure({}), Success({})),
15+
(IOFailure(1), IOSuccess(1)),
16+
])
17+
def test_swap_results(container, swapped):
18+
"""Ensures that `swap` is always returning the correct type."""
19+
assert swap(container) == swapped
20+
21+
22+
@pytest.mark.parametrize('container', [
23+
Success({}),
24+
IOSuccess(1),
25+
26+
Failure('a'),
27+
IOFailure(True),
28+
])
29+
def test_swap_twice(container):
30+
"""Ensures that `swap` is always returning the correct type."""
31+
assert swap(swap(container)) == container

typesafety/test_converters/test_flatten.yml

Lines changed: 19 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
- case: join_io
1+
- case: flatten_io
22
disable_cache: true
33
main: |
44
from returns.converters import flatten
@@ -7,7 +7,7 @@
77
reveal_type(flatten(IO(IO(1)))) # N: Revealed type is 'returns.io.IO[builtins.int*]'
88
99
10-
- case: join_maybe
10+
- case: flatten_maybe
1111
disable_cache: true
1212
main: |
1313
from returns.converters import flatten
@@ -16,7 +16,7 @@
1616
reveal_type(flatten(Some(Some(1)))) # N: Revealed type is 'returns.maybe.Maybe[builtins.int*]'
1717
1818
19-
- case: join_result1
19+
- case: flatten_result1
2020
disable_cache: true
2121
main: |
2222
from returns.converters import flatten
@@ -28,7 +28,7 @@
2828
reveal_type(flatten(returns_result())) # N: Revealed type is 'returns.result.Result[builtins.int*, builtins.str*]'
2929
3030
31-
- case: join_result2
31+
- case: flatten_result2
3232
disable_cache: true
3333
main: |
3434
from returns.converters import flatten
@@ -42,7 +42,7 @@
4242
main:7: error: Argument 1 to "_flatten" has incompatible type "Result[int, Result[int, str]]"; expected "Result[Result[<nothing>, Result[int, str]], Result[int, str]]"
4343
4444
45-
- case: join_ioresult1
45+
- case: flatten_ioresult1
4646
disable_cache: true
4747
main: |
4848
from returns.converters import flatten
@@ -54,7 +54,7 @@
5454
reveal_type(flatten(returns_ioresult())) # N: Revealed type is 'returns.io.IOResult[builtins.int*, builtins.str*]'
5555
5656
57-
- case: join_ioresult2
57+
- case: flatten_ioresult2
5858
disable_cache: true
5959
main: |
6060
from returns.converters import flatten
@@ -68,7 +68,7 @@
6868
main:7: error: Argument 1 to "_flatten" has incompatible type "IOResult[int, IOResult[int, str]]"; expected "IOResult[IOResult[<nothing>, IOResult[int, str]], IOResult[int, str]]"
6969
7070
71-
- case: join_context
71+
- case: flatten_context
7272
disable_cache: true
7373
main: |
7474
from returns.converters import flatten
@@ -79,7 +79,7 @@
7979
reveal_type(flatten(x)) # N: Revealed type is 'returns.context.requires_context.RequiresContext[builtins.str*, builtins.int*]'
8080
8181
82-
- case: join_context_result
82+
- case: flatten_context_result
8383
disable_cache: true
8484
main: |
8585
from returns.converters import flatten
@@ -88,3 +88,14 @@
8888
x: RequiresContextResult[str, RequiresContextResult[str, int, float], float]
8989
9090
reveal_type(flatten(x)) # N: Revealed type is 'returns.context.requires_context_result.RequiresContextResult[builtins.str*, builtins.int*, builtins.float*]'
91+
92+
93+
- case: flatten_context_io_result
94+
disable_cache: true
95+
main: |
96+
from returns.converters import flatten
97+
from returns.context import RequiresContextIOResult
98+
99+
x: RequiresContextIOResult[str, RequiresContextIOResult[str, int, float], float]
100+
101+
reveal_type(flatten(x)) # N: Revealed type is 'returns.context.requires_context_io_result.RequiresContextIOResult[builtins.str*, builtins.int*, builtins.float*]'

0 commit comments

Comments
 (0)