File: future.rst

package info (click to toggle)
python-returns 0.26.0-1
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid
  • size: 2,652 kB
  • sloc: python: 11,000; makefile: 18
file content (258 lines) | stat: -rw-r--r-- 7,280 bytes parent folder | download | duplicates (2)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
Future
======

A set of primitives to work with ``async`` functions.

Can be used with ``asyncio``, ``trio``, and ``curio``.
And any event-loop!

Tested with `anyio <https://github.com/agronholm/anyio>`_.

What problems do we solve with these containers? Basically these ones:

1. You cannot call async function from a sync one
2. Any unexpectedly thrown exception can ruin your whole event loop
3. Ugly composition with lots of ``await`` statements


Future container
----------------

Without ``Future`` container it is impossible to compose two functions:
sync and async one.

You simply cannot ``await`` coroutines inside a sync context.
It is even a ``SyntaxError``.

.. code:: python

  def test():
      await some()
  # SyntaxError: 'await' outside async function

So, you have to turn you function into async one.
And all callers of this function in async functions. And all their callers.

This is really hard to model.
When your code has two types of uncomposable
functions you increase your mental complexity by extreme levels.

Instead, you can use ``Future`` container,
it allows you to model async interactions in a sync manner:

.. code:: pycon

  >>> from returns.future import Future

  >>> async def first() -> int:
  ...     return 1

  >>> async def second(arg: int) -> int:
  ...     return arg + 1

  >>> def main() -> Future[int]:  # sync function!
  ...    return Future(first()).bind_awaitable(second)

Now we can compose async functions and maintaining a sync context!
It is also possible to run a ``Future``
with regular tools like ``asyncio.run`` or ``anyio.run``:

.. code:: python

  >>> import anyio
  >>> from returns.io import IO

  >>> assert anyio.run(main().awaitable) == IO(2)

One more very useful thing ``Future`` does behind the scenes is converting
its result to ``IO``-based containers.
This helps a lot when separating pure and impure
(async functions are impure) code inside your app.


FutureResult
------------

This container becomes very useful when working
with ``async`` function that can fail.

It works the similar way regular ``Result`` does.
And is literally a wrapper around ``Future[Result[_V, _E]]`` type.

Let's see how it can be used in a real program:

.. literalinclude:: ../../tests/test_examples/test_future/test_future_result.py
   :linenos:

What is different?

1. We can now easily make ``show_titles`` sync,
   we can also make ``_fetch_post`` sync,
   but we would need to use ``ReaderFutureResult`` container
   with proper dependencies in this case
2. We now don't care about errors at all.
   In this example any error will cancel the whole pipeline
3. We now have ``.map`` method to easily compose sync and async functions

You can see the next example
with :ref:`RequiresContextFutureResult <requires_context_future_result>`
and without a single ``async/await``.
That example illustrates the whole point of our actions: writing
sync code that executes asynchronously without any magic at all.



Decorators
----------

future
~~~~~~

This decorator helps to easily transform ``async def`` into ``Future``:

.. code:: python

  >>> import anyio
  >>> from returns.future import future, Future
  >>> from returns.io import IO

  >>> @future
  ... async def test(arg: int) -> float:
  ...     return arg / 2

  >>> future_instance = test(1)
  >>> assert isinstance(future_instance, Future)
  >>> assert anyio.run(future_instance.awaitable) == IO(0.5)

Make sure that you decorate with ``@future`` only coroutines
that do not throw exceptions. For ones that do, use ``future_safe``.

future_safe
~~~~~~~~~~~

This decorator converts ``async def`` into ``FutureResult``,
which means that it becomes:

1. Full featured ``Future`` like container
2. Safe from any exceptions

Let's dig into it:

.. code:: pycon

  >>> import anyio
  >>> from returns.future import future_safe, FutureResult
  >>> from returns.io import IOSuccess, IOFailure

  >>> @future_safe
  ... async def test(arg: int) -> float:
  ...     return 1 / arg

  >>> future_instance = test(2)
  >>> assert isinstance(future_instance, FutureResult)
  >>> assert anyio.run(future_instance.awaitable) == IOSuccess(0.5)

  >>> str(anyio.run(test(0).awaitable))  # this will fail
  '<IOResult: <Failure: division by zero>>'

Never miss exceptions ever again!

asyncify
~~~~~~~~

Helper decorator to transform regular sync function into ``async`` ones.

.. code:: python

  >>> import anyio
  >>> from inspect import iscoroutinefunction
  >>> from returns.future import asyncify

  >>> @asyncify
  ... def your_function(x: int) -> int:
  ...     return x + 1

  >>> assert iscoroutinefunction(your_function) is True
  >>> assert anyio.run(your_function, 1) == 2

Very important node: making your function ``async`` does not mean
it will work asynchronously. It can still block if it uses blocking calls.
Here's an example of how you **must not** do:

.. code:: python

  import requests
  from returns.future import asyncify

  @asyncify
  def please_do_not_do_that():
      return requests.get('...')  # this will still block!

Do not overuse this decorator.
It is only useful for some basic composition
with ``Future`` and ``FutureResult``.


FAQ
---

Is it somehow related to Future object from asyncio?
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

Nope, we just use the same naming there are in other languages and platforms.
Python happens to have its own meaning for this word.

In our worldview, these two ``Future`` entities should never meet each other
in a single codebase.

It is also not related to `concurrent.Future <https://docs.python.org/3/library/concurrent.futures.html>`_.

How to create unit objects?
~~~~~~~~~~~~~~~~~~~~~~~~~~~

For ``Future`` container:

- ``from_value`` when you have a raw value
- ``from_io`` when you have existing ``IO`` container
- ``from_future_result`` when you have existing ``FutureResult``

For ``FutureResult`` container:

- ``from_value`` when you want to mark some raw value as a ``Success``
- ``from_failure`` when you want to mark some raw value as a ``Failure``
- ``from_result`` when you already have ``Result`` container
- ``from_io`` when you have successful ``IO``
- ``from_failed_io`` when you have failed ``IO``
- ``from_future`` when you have successful ``Future``
- ``from_failed_future`` when you have failed ``Future``
- ``from_typecast`` when you have existing ``Future[Result]``

What is the difference between Future[Result[a, b]] and FutureResult[a, b]?
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

There's almost none.

The only difference is that ``FutureResult[a, b]`` is a handy wrapper
around ``Future[Result[a, b]]``,
so you won't need to use methods like ``.map`` and ``.bind`` twice.

You can always convert it with methods like
``.from_typecast`` and ``.from_future_result``.


Further reading
---------------

- `How Async Should Have Been <https://sobolevn.me/2020/06/how-async-should-have-been>`_
- `What Color is Your Function? <https://journal.stuffwithstuff.com/2015/02/01/what-color-is-your-function/>`_
- `From Promises to Futures <https://dev.to/nadeesha/from-promises-to-futures-in-javascript-2m6g>`_


API Reference
-------------

.. autoclasstree:: returns.future
   :strict:

.. automodule:: returns.future
   :members: