Skip to content

Commit d630305

Browse files
committed
Adds .bind_async and .bind_awaitable, refs #274
1 parent c53179e commit d630305

File tree

9 files changed

+200
-16
lines changed

9 files changed

+200
-16
lines changed

docs/pages/context.rst

Lines changed: 0 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -260,9 +260,6 @@ so you can work easily with it:
260260

261261
.. currentmodule:: returns.context.requires_context_result
262262

263-
- :meth:`~RequiresContextResult.from_typecast`
264-
turns accidental ``RequiresContext[env, Result[a, b]]`` into
265-
full-featured ``RequiresContextResult[env, a, b]``
266263
- :meth:`~RequiresContextResult.bind_result`
267264
allows to bind functions that return ``Result`` with just one call
268265
- :meth:`~RequiresContextResult.bind_context`
@@ -283,9 +280,6 @@ so you can work easily with it:
283280

284281
.. currentmodule:: returns.context.requires_context_ioresult
285282

286-
- :meth:`~RequiresContextIOResult.from_typecast`
287-
turns accidental ``RequiresContext[env, IOResult[a, b]]`` into
288-
full-featured ``RequiresContextIOResult[env, a, b]``
289283
- :meth:`~RequiresContextIOResult.bind_result`
290284
allows to bind functions that return ``Result`` with just one call
291285
- :meth:`~RequiresContextIOResult.bind_io`
@@ -381,9 +375,6 @@ These methods are identical with ``RequiresContextIOResult``:
381375

382376
.. currentmodule:: returns.context.requires_context_future_result
383377

384-
- :meth:`~RequiresContextFutureResult.from_typecast`
385-
turns accidental ``RequiresContext[env, IOResult[a, b]]`` into
386-
full-featured ``RequiresContextFutureResult[env, a, b]``
387378
- :meth:`~RequiresContextFutureResult.bind_result`
388379
allows to bind functions that return ``Result`` with just one call
389380
- :meth:`~RequiresContextFutureResult.bind_io`

returns/_generated/futures/_future_result.py

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ async def async_bind(
4141
container = await inner_value
4242
if isinstance(container, Result.success_type):
4343
return (await function(container.unwrap()))._inner_value
44-
return container # type: ignore
44+
return container # type: ignore[return-value]
4545

4646

4747
async def async_bind_awaitable(
@@ -52,7 +52,7 @@ async def async_bind_awaitable(
5252
container = await inner_value
5353
if isinstance(container, Result.success_type):
5454
return Result.from_value(await function(container.unwrap()))
55-
return container # type: ignore
55+
return container # type: ignore[return-value]
5656

5757

5858
async def async_bind_async(
@@ -66,7 +66,7 @@ async def async_bind_async(
6666
container = await inner_value
6767
if isinstance(container, Result.success_type):
6868
return await (await function(container.unwrap()))._inner_value
69-
return container # type: ignore
69+
return container # type: ignore[return-value]
7070

7171

7272
async def async_bind_result(
@@ -85,7 +85,7 @@ async def async_bind_ioresult(
8585
container = await inner_value
8686
if isinstance(container, Result.success_type):
8787
return function(container.unwrap())._inner_value
88-
return container # type: ignore
88+
return container # type: ignore[return-value]
8989

9090

9191
async def async_bind_io(
@@ -96,7 +96,7 @@ async def async_bind_io(
9696
container = await inner_value
9797
if isinstance(container, Result.success_type):
9898
return Success(function(container.unwrap())._inner_value)
99-
return container # type: ignore
99+
return container # type: ignore[return-value]
100100

101101

102102
async def async_bind_future(
@@ -107,7 +107,7 @@ async def async_bind_future(
107107
container = await inner_value
108108
if isinstance(container, Result.success_type):
109109
return await async_from_success(function(container.unwrap()))
110-
return container # type: ignore
110+
return container # type: ignore[return-value]
111111

112112

113113
async def async_fix(
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
from typing import TYPE_CHECKING, Awaitable, Callable, TypeVar
2+
3+
from returns.result import Result
4+
5+
if TYPE_CHECKING:
6+
from returns.context import RequiresContextFutureResult # noqa: F401
7+
8+
_ValueType = TypeVar('_ValueType', covariant=True)
9+
_NewValueType = TypeVar('_NewValueType')
10+
_ErrorType = TypeVar('_ErrorType', covariant=True)
11+
_EnvType = TypeVar('_EnvType')
12+
13+
14+
async def async_bind_async(
15+
function: Callable[
16+
[_ValueType],
17+
Awaitable[
18+
'RequiresContextFutureResult[_EnvType, _NewValueType, _ErrorType]',
19+
],
20+
],
21+
container: 'RequiresContextFutureResult[_EnvType, _ValueType, _ErrorType]',
22+
deps: _EnvType,
23+
) -> Result[_NewValueType, _ErrorType]:
24+
"""Async binds a coroutine with container over a value."""
25+
inner_value = await container(deps)._inner_value
26+
if isinstance(inner_value, Result.success_type):
27+
return await (await function(inner_value.unwrap()))(deps)._inner_value
28+
return inner_value # type: ignore[return-value]

returns/_generated/pointfree/bind_async.pyi

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,12 @@
11
from typing import Awaitable, Callable, TypeVar, overload
22

3+
from returns.context import RequiresContextFutureResult
34
from returns.future import Future, FutureResult
45

56
_ValueType = TypeVar('_ValueType', contravariant=True)
67
_ErrorType = TypeVar('_ErrorType')
78
_NewValueType = TypeVar('_NewValueType', covariant=True)
9+
_EnvType = TypeVar('_EnvType')
810

911

1012
@overload
@@ -25,3 +27,18 @@ def _bind_async(
2527
FutureResult[_NewValueType, _ErrorType],
2628
]:
2729
...
30+
31+
32+
@overload
33+
def _bind_async(
34+
function: Callable[
35+
[_ValueType],
36+
Awaitable[RequiresContextFutureResult[
37+
_EnvType, _NewValueType, _ErrorType,
38+
]],
39+
],
40+
) -> Callable[
41+
[RequiresContextFutureResult[_EnvType, _ValueType, _ErrorType]],
42+
RequiresContextFutureResult[_EnvType, _NewValueType, _ErrorType],
43+
]:
44+
...

returns/_generated/pointfree/bind_awaitable.pyi

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,13 @@ from typing import Awaitable, Callable, TypeVar, overload
22

33
from typing_extensions import Protocol
44

5+
from returns.context import RequiresContextFutureResult
56
from returns.future import Future, FutureResult
67

78
_ValueType = TypeVar('_ValueType', contravariant=True)
89
_ErrorType = TypeVar('_ErrorType')
910
_NewValueType = TypeVar('_NewValueType', covariant=True)
11+
_EnvType = TypeVar('_EnvType')
1012

1113

1214
class _BindAwaitable(Protocol[_ValueType, _NewValueType]):
@@ -33,6 +35,15 @@ class _BindAwaitable(Protocol[_ValueType, _NewValueType]):
3335
) -> FutureResult[_NewValueType, _ErrorType]:
3436
...
3537

38+
@overload
39+
def __call__(
40+
self,
41+
container: RequiresContextFutureResult[
42+
_EnvType, _ValueType, _ErrorType,
43+
],
44+
) -> RequiresContextFutureResult[_EnvType, _NewValueType, _ErrorType]:
45+
...
46+
3647

3748
def _bind_awaitable(
3849
function: Callable[[_ValueType], Awaitable[_NewValueType]],

returns/context/requires_context_future_result.py

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

1515
from typing_extensions import final
1616

17-
from returns._generated.futures import _future_result
17+
from returns._generated.futures import _future_result, _reader_future_result
1818
from returns._generated.iterable import iterable
1919
from returns.context import NoDeps
2020
from returns.future import Future, FutureResult
@@ -264,6 +264,89 @@ def bind(
264264
),
265265
)
266266

267+
def bind_async(
268+
self,
269+
function: Callable[
270+
[_ValueType],
271+
Awaitable[
272+
'RequiresContextFutureResult'
273+
'[_EnvType, _NewValueType, _ErrorType]'
274+
],
275+
],
276+
) -> 'RequiresContextFutureResult[_EnvType, _NewValueType, _ErrorType]':
277+
"""
278+
Composes this container with a async function returning the same type.
279+
280+
.. code:: python
281+
282+
>>> import anyio
283+
>>> from returns.context import RequiresContextFutureResult
284+
>>> from returns.io import IOSuccess, IOFailure
285+
286+
>>> async def function(
287+
... number: int,
288+
... ) -> RequiresContextFutureResult[int, str, int]:
289+
... return RequiresContextFutureResult.from_value(number + 1)
290+
291+
>>> assert anyio.run(
292+
... RequiresContextFutureResult.from_value(1).bind_async(
293+
... function,
294+
... ),
295+
... RequiresContextFutureResult.empty,
296+
... ) == IOSuccess(2)
297+
>>> assert anyio.run(
298+
... RequiresContextFutureResult.from_failure(1).bind_async(
299+
... function,
300+
... ),
301+
... RequiresContextFutureResult.empty,
302+
... ) == IOFailure(1)
303+
304+
"""
305+
return RequiresContextFutureResult(
306+
lambda deps: FutureResult(_reader_future_result.async_bind_async(
307+
function, self, deps,
308+
)),
309+
)
310+
311+
def bind_awaitable(
312+
self,
313+
function: Callable[[_ValueType], 'Awaitable[_NewValueType]'],
314+
) -> 'RequiresContextFutureResult[_EnvType, _NewValueType, _ErrorType]':
315+
"""
316+
Allows to compose a container and a regular ``async`` function.
317+
318+
This function should return plain, non-container value.
319+
See :meth:`~RequiresContextFutureResult.bind_async`
320+
to bind ``async`` function that returns a container.
321+
322+
.. code:: python
323+
324+
>>> import anyio
325+
>>> from returns.context import RequiresContextFutureResult
326+
>>> from returns.io import IOSuccess, IOFailure
327+
328+
>>> async def coroutine(x: int) -> int:
329+
... return x + 1
330+
331+
>>> assert anyio.run(
332+
... RequiresContextFutureResult.from_value(1).bind_awaitable(
333+
... coroutine,
334+
... ),
335+
... RequiresContextFutureResult.empty,
336+
... ) == IOSuccess(2)
337+
338+
>>> assert anyio.run(
339+
... RequiresContextFutureResult.from_failure(1).bind_awaitable(
340+
... coroutine,
341+
... ),
342+
... RequiresContextFutureResult.empty,
343+
... ) == IOFailure(1)
344+
345+
"""
346+
return RequiresContextFutureResult(
347+
lambda deps: self(deps).bind_awaitable(function),
348+
)
349+
267350
def bind_result(
268351
self,
269352
function: Callable[[_ValueType], 'Result[_NewValueType, _ErrorType]'],

typesafety/test_context/test_requires_context_future_result/test_requires_context_future_result.yml

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,32 @@
3333
reveal_type(x.bind(test)) # N: Revealed type is 'returns.context.requires_context_future_result.RequiresContextFutureResult[builtins.str, builtins.bool*, builtins.float]'
3434
3535
36+
- case: requires_context_future_result_bind_awaitable
37+
disable_cache: true
38+
main: |
39+
from returns.context import RequiresContextFutureResult
40+
41+
async def bind_awaitable(arg: int) -> float:
42+
...
43+
44+
first: RequiresContextFutureResult[bool, int, str]
45+
46+
reveal_type(first.bind_awaitable(bind_awaitable)) # N: Revealed type is 'returns.context.requires_context_future_result.RequiresContextFutureResult[builtins.bool, builtins.float*, builtins.str]'
47+
48+
49+
- case: requires_context_future_result_bind_async
50+
disable_cache: true
51+
main: |
52+
from returns.context import RequiresContextFutureResult
53+
54+
async def bind_async(arg: int) -> RequiresContextFutureResult[bool, float, str]:
55+
...
56+
57+
first: RequiresContextFutureResult[bool, int, str]
58+
59+
reveal_type(first.bind_async(bind_async)) # N: Revealed type is 'returns.context.requires_context_future_result.RequiresContextFutureResult[builtins.bool, builtins.float*, builtins.str]'
60+
61+
3662
- case: requires_context_future_result_bind_result
3763
disable_cache: true
3864
main: |

typesafety/test_pointfree/test_bind_async.yml

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,3 +22,17 @@
2222
2323
x: FutureResult[float, str]
2424
reveal_type(bind_async(test)(x)) # N: Revealed type is 'returns.future.FutureResult[builtins.int*, builtins.str*]'
25+
26+
27+
- case: bind_async_requires_context_future_result
28+
disable_cache: true
29+
main: |
30+
from returns.pointfree import bind_async
31+
from returns.context import RequiresContextFutureResult
32+
33+
async def test(arg: int) -> RequiresContextFutureResult[bool, float, str]:
34+
...
35+
36+
first: RequiresContextFutureResult[bool, int, str]
37+
38+
reveal_type(bind_async(test)(first)) # N: Revealed type is 'returns.context.requires_context_future_result.RequiresContextFutureResult[builtins.bool*, builtins.float*, builtins.str*]'

typesafety/test_pointfree/test_bind_awaitable.yml

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,3 +58,17 @@
5858
5959
x: FutureResult[float, str]
6060
reveal_type(bind_awaitable(test)(x)) # N: Revealed type is 'returns.future.FutureResult[builtins.int, builtins.str*]'
61+
62+
63+
- case: bind_awaitable_requires_context_future_result
64+
disable_cache: true
65+
main: |
66+
from returns.pointfree import bind_awaitable
67+
from returns.context import RequiresContextFutureResult
68+
69+
async def test(arg: int) -> float:
70+
...
71+
72+
first: RequiresContextFutureResult[bool, int, str]
73+
74+
reveal_type(bind_awaitable(test)(first)) # N: Revealed type is 'returns.context.requires_context_future_result.RequiresContextFutureResult[builtins.bool*, builtins.float, builtins.str*]'

0 commit comments

Comments
 (0)